/*
    PIP - Platform Independent Primitives
    Deploy tool
    Ivan Pelipenko peri4ko@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "picli.h"
#include "pidir.h"
#include "piiostream.h"

#define DELIM "::"

using namespace PICoutManipulators;

PIString cmd_copy, cmd_copydir, cmd_suffix, ign_err_suffix;
PIString qplatforms;

void setCommands() {
#ifdef WINDOWS
	cmd_copy       = "copy /y ";
	cmd_copydir    = "copy /y ";
	cmd_suffix     = " 1> NUL";
	ign_err_suffix = " 2> NUL";
	qplatforms     = "windows";
#else
	cmd_copy       = "cp -f ";
	cmd_copydir    = "cp -rf ";
	ign_err_suffix = " 2> /dev/null";
#  ifdef MAC_OS
	qplatforms     = "cocoa";
#  else
	qplatforms = "xcb,wayland";
#  endif
#endif
}


void usage() {
	piCout << Bold << "Deploy tool";
	piCout << Cyan << "Version PIP" << Bold << PIPVersion();
	piCout << "";
	piCout << "Copy all dependencies of <file> to directory <out_path>";
	piCout << "You can specify any file or directory as <file>.";
	piCout << "In case of directory it will be scan recursively.";
	piCout << "";
	piCout << "If some Qt dependency found, copy corresponding Qt plugins.";
	piCout << "Styles and platforms selected by -S and -P flags,";
	piCout << "any other plugins described by --qt-plugins flag in next format:";
	piCout << "\"[*=" DELIM "]<plugins>=<regexp>,<regexp>" DELIM "<plugins>=<regexp>,<regexp>\", e.g.";
	piCout << "\"sqldrivers=lite,mysql" DELIM "geoservices=" DELIM "position=nmea\".";
	piCout << "If no regexp specified for plugins, nothing will be copied.";
	piCout << "Default regexp set by \"*=<regexp>\".";
	piCout << "\"*=\" disable optional plugins.";
	piCout << "";
	piCout << Green << Bold << "Usage:" << Default
		   << "\"deploy_tool [-hvfC] [--dependencies [--prefix <text>]] "
			  "[--qt-plugins-dir <d>] [-s <search_path>] [--ignore <libs>] [-S <styles>] "
			  "[-l <ldd>] [-D <dpkg>] [--dpkg-workdir <d>] [-L <readelf> | -W <objdump> | -M <otool>] "
			  "[--name-tool <pathS>] [--rpath] [-d <depth>] [-q <qtdir>] [-a <add_libs>] [-S <styles>] "
			  "[-P <platforms>] [--qt-plugins <d>] [--qt-modules <l>] [--qt-conf-dir <d>] -o <out_path> <file> [<file2> ...]\""
		   << NewLine;
	piCout << Green << Bold << "Details:";
	piCout << Bold << "Debug control";
	piCout << "-h, --help           " << Green << "- display this message and exit";
	piCout << "-v, --verbose        " << Green << "- be verbose";
	piCout << "";
	piCout << Bold << "Processing control";
	piCout << "-f, --fake           " << Green << "- don`t copy, only print";
	piCout << "-s <search_path>     " << Green
		   << "- set search pathes for system libraries, may be separated by \"" DELIM "\", default \"/usr/lib\"";
	piCout << "--ignore <libs>      " << Green << "- ignore libraries names, may be separated by \"" DELIM "\", default \"\"";
	piCout << "-l <ldd>             " << Green << "- \"ldd\" path, default \"/usr/bin/ldd\"";
	piCout << "-L <readelf>         " << Green << "- \"readelf\" path, overrides \"ldd\"";
	piCout << "-W <objdump>         " << Green << "- \"objdump\" path, overrides \"ldd\"";
	piCout << "-M <otool>           " << Green << "- \"otool\" path, overrides \"ldd\"";
	piCout << "-D <dpkg>            " << Green << "- \"dpkg\" path, default \"/usr/bin/dpkg\"";
	piCout << "--dpkg-workdir <d>   " << Green << "- dpkg \"admindir\" path, default \"\"";
	piCout << "-R <rpm>             " << Green << "- \"rpm\" path, overrides \"dpkg\" for dependency queries";
	piCout << "--rpm-rootdir <d>    " << Green << "- rpm \"--root\" path, default \"\"";
	piCout << "--name-tool <path>   " << Green << "- \"install_name_tool\" path, default \"install_name_tool\"";
	piCout << "--strip <path>       " << Green << "- \"strip\" path, default \"strip\"";
	piCout << "--no-strip           " << Green << "- disable strip (save debug info)";
	piCout << "--rpath              " << Green << "- set rpath for copied files using \"patchelf\"";
	piCout << "-d <depth>           " << Green << "- maximum dependepcies depth, default 8";
	piCout << "";
	piCout << Bold << "Qt control";
	piCout << "-q <qtdir>           " << Green << "- path where Qt root dir, default takes from \"qmake -v\"";
	piCout << "-S <styles>          " << Green << "- set Qt styles (e.g. \"oxygen,breeze\"), default \"*\"";
	piCout << "-P <platforms>       " << Green << "- set Qt platforms (e.g. \"win,mini\"), default by host system";
	piCout << "--qt-plugins <d>     " << Green << "- set Qt plugins description";
	piCout << "--qt-modules <list>  " << Green << "- additional Qt modules, may be separated by \"" DELIM "\" (e.g. \"Sql" DELIM "Xml\")";
	piCout << "--qml-modules <list> " << Green
		   << "- additional Qt QML modules, may be separated by \"" DELIM "\" (e.g. \"Qt" DELIM "QtQuick.2\")";
	piCout << "";
	piCout << Bold << "Output control";
	piCout << "-o <out_path>        " << Green << "- path for libraries copy to";
	piCout << "--qt-plugins-dir <d> " << Green << "- dir where place Qt plugins, default \"<out_path>\"";
	piCout << "--qt-qml-dir <d>     " << Green << "- dir where place Qt QML modules, default \"<out_path>\"";
	piCout << "--qt-conf-dir <dir>  " << Green << "- dir where place \"qt.conf\", default \"<out_path>\"";
	piCout << "--dependencies       " << Green << "- search dependencies by <dpkg> or <rpm>, print them and copy missing libraries";
	piCout << "--prefix <text>      " << Green << "- print <text> before dependencies";
	piCout << "";
	piCout << Bold << "Input control";
	piCout << "<file> ...           " << Green << "- executable to process";
	piCout << "-a <add_libs>        " << Green
		   << "- additional libs, separated by \"" DELIM "\". Libraries will be searched in <search_path>";
}

struct QtDep {
	QtDep(const PIString & l = PIString(), const PIStringList & p = PIStringList()): lib(l), plugins(p) {}
	PIString lib;
	PIStringList plugins;
};
QtDep qt_deps[] = {QtDep("core", PIStringList() << "platforms"),
                   QtDep("gui", PIStringList() << "imageformats"),
                   QtDep("widgets", PIStringList() << "styles"),
                   QtDep("sql", PIStringList() << "sqldrivers"),
                   QtDep("positioning", PIStringList() << "position"),
                   QtDep("location", PIStringList() << "geoservices"),
                   QtDep("multimedia",
                         PIStringList() << "audio"
                                        << "mediaservice"
                                        << "playlistformats"),
                   QtDep("printsupport", PIStringList() << "printsupport"),
                   QtDep("virtualkeyboard", PIStringList() << "platforminputcontexts"),
                   QtDep("sensors",
                         PIStringList() << "sensors"
                                        << "sensorgestures"),
                   QtDep("texttospeech", PIStringList() << "texttospeech"),
                   QtDep("serialbus", PIStringList() << "canbus"),
                   QtDep()};


int depth       = 8;
bool fake = false, is_ldd = true, is_deps = false, need_qt = false, make_qt_format = true, rpath = false, win_target = false,
	 use_rpm = false;
PIString ldd, readelf, objdump, otool, dpkg, rpm_tool, nametool, strip, out_dir, qt_dir, dpkg_workdir, rpm_rootdir;
PIString qt_pref, qt_suff, qt_conf_dir, qt_plugins_dir, qt_qml_dir, target_dir;
PIStringList styles, lib_dirs, add_libs, platforms, sqldrivers, input_files, plugin_libs, qt_add_libs, qml_list;
PISet<PIString> all_libs, miss_libs, all_deps, frameworks, framework_libs, miss_frameworks, qt_plugins, ignore_libs, qt_libs;
PIMap<PIString, PIStringList> qt_filters;


PIString findLib(const PIString & l) {
	if (PIFile::isExists(l)) return l;
	for (const auto & s: lib_dirs) {
		if (PIFile::isExists(s + l)) return s + l;
		if (win_target || l.toLowerCase().endsWith("dll")) {
			PIFile::FileInfo info(l);
			PIString fn = info.baseName(), fe = info.extension(), nn;

			nn = s + fn + "." + fe.toLowerCase();
			if (PIFile::isExists(nn)) return nn;
			nn = s + fn + "." + fe.toUpperCase();
			if (PIFile::isExists(nn)) return nn;

			nn = s + fn.toLowerCase() + "." + fe;
			if (PIFile::isExists(nn)) return nn;
			nn = s + fn.toLowerCase() + "." + fe.toLowerCase();
			if (PIFile::isExists(nn)) return nn;
			nn = s + fn.toLowerCase() + "." + fe.toUpperCase();
			if (PIFile::isExists(nn)) return nn;

			nn = s + fn.toUpperCase() + "." + fe;
			if (PIFile::isExists(nn)) return nn;
			nn = s + fn.toUpperCase() + "." + fe.toLowerCase();
			if (PIFile::isExists(nn)) return nn;
			nn = s + fn.toUpperCase() + "." + fe.toUpperCase();
			if (PIFile::isExists(nn)) return nn;
		}
	}
	return "";
}


PIString frameworkName(const PIString & l) {
	if (!l.contains(".framework/")) return "";
	PIStringList ll = l.split("/");
	piForeachRC(PIString & _f, ll) {
		if (_f.endsWith(".framework")) {
			return _f;
		}
	}
	return "";
}


PIString frameworkInternalPath(const PIString & l) {
	return l.right(l.size_s() - l.findLast('.') - 11);
}


PIString execute(const PIString & cmd) {
	FILE * fp = popen(cmd.dataAscii(), "r");
	PIString ret;
	if (fp) {
		const int sz = 256;
		char in[sz];
		piZeroMemory(in, sz);
		while (true) {
			int r = fread(in, 1, sz, fp);
			if (r <= 0) break;
			ret += PIString(in, r);
		}
		pclose(fp);
	}
	return ret;
}


PIStringList filter(const PIString & in, const PIString & f) {
	PIStringList ret, lines = in.split("\n");
	for (const PIString & l: lines)
		if (l.contains(f)) ret << l.trimmed();
	return ret;
}


void checkQtLib(PIString lib) {
	PIString base = lib.toLowerCase(), pref, suff;
	if (base.startsWith("lib")) {
		pref += "lib";
		base.cutLeft(3);
	}
	if (base.startsWith("qt5")) {
		pref += "Qt5";
		base.cutLeft(3);
	}
	if (base.startsWith("qt6")) {
		pref += "Qt6";
		base.cutLeft(3);
	}
	if (base.startsWith("qt")) {
		pref += "Qt";
		base.cutLeft(2);
	}
	if (base.find('.') >= 0) {
		suff = base.right(base.size_s() - base.find('.'));
		base = base.left(base.find('.'));
	}
	for (int i = 0;; ++i) {
		if (qt_deps[i].lib.isEmpty()) break;
		if (qt_deps[i].lib == base) {
			qt_plugins << PISet<PIString>(qt_deps[i].plugins);
			// piCout << "add qt plugins" << qt_deps[i].plugins << "now" << qt_plugins;
			need_qt = true;
			qt_libs << lib;
			if (make_qt_format) {
				make_qt_format = false;
				qt_pref        = pref;
				qt_suff        = suff;
			}
			break;
		}
	}
}


void procLdd(PIString file, bool ext_lib = false, int cur_depth = 0) {
	++cur_depth;
	if (cur_depth > depth) return;
	static PIStringList ignore_ext = {"png",      "jpg", "jpeg", "gif",  "bmp", "svg",  "tif",  "tiff", "ico",   "pdf",
	                                  "txt",      "cfg", "conf", "json", "qml", "glsl", "fraq", "vert", "geom",  "qmltypes",
	                                  "metainfo", "ts",  "qm",   "ttf",  "htm", "html", "md",   "sms",  "smsee", "blockmodel"};
	PIString ext                   = PIFile::FileInfo(file).extension();
	if (ignore_ext.contains(ext.toLowerCase())) return;
	piCout << "scan" << file << "...";
	PISet<PIString> cur_libs;
	if (ext_lib) {
		if (!all_libs[file]) {
			cur_libs << file;
			all_libs << file;
		}
	}
	PIStringList lines;
	if (is_ldd) {
		lines = execute(ldd + " \"" + file + "\"").split("\n");
	} else {
		PIString cmd;
		if (!readelf.isEmpty()) {
			lines = filter(execute(readelf + " -a \"" + file + "\""), "Shared library:");
			for (PIString & l: lines) {
				l.cutRight(1);
				l.cutLeft(l.find('[') + 1);
				l.trim(); //.append('.').prepend('.');
			}
		}
		if (!objdump.isEmpty()) {
			PIString out = execute(objdump + " -p \"" + file + "\"" + ign_err_suffix);
			lines        = filter(out, "DLL Name:");
			lines << filter(out, "NEEDED");
			for (PIString & l: lines) {
				if (l.startsWith("DLL"))
					l.cutLeft(9);
				else
					l.cutLeft(6);
				l.trim(); //.append('.').prepend('.');
			}
		}
		if (!otool.isEmpty()) {
			lines = filter(execute(otool + " -L \"" + file + "\""), "(");
			for (auto & l: lines) {
				l = l.left(l.find('('));
				l.trim(); //.append('.').prepend('.');
			}
		}
	}
	for (const auto & sl: lines) {
		PIString l = sl.trimmed();
		if (!otool.isEmpty()) {
			PIString fname = frameworkName(l);
			if (!fname.isEmpty()) {
				frameworks << fname;
				framework_libs << l.cutLeft(1).cutRight(1);
				checkQtLib(fname);
				continue;
			}
		}
		if (is_ldd) {
			if (!l.contains("=>")) continue;
			l.cutLeft(l.find("=>") + 2);
			if (l.contains("(")) l.cutRight(l.length() - l.find("("));
			l.trim();
			if (l.toLowerCase() == "not found") {
				miss_libs << sl.left(sl.find("=>")).trim();
				continue;
			}
		} else {
			// l.cutLeft(1).cutRight(1).trim();
			if (l.isEmpty()) continue;
			if (!otool.isEmpty()) {
				if (!l.startsWith("/usr/local/")) {
					PIFile::FileInfo fi;
					fi.path = l;
					l       = fi.name();
				}
			}
			PIString flp = findLib(l);
			if (flp.isEmpty()) {
				// piCout << "Can`t find" << l;
				miss_libs << l;
				continue;
			}
			l = flp;
		}
		if (all_libs[l]) continue;
		PIFile::FileInfo fi;
		fi.path        = l;
		PIString lname = fi.baseName();
		if (lname.startsWith("lib")) lname.cutLeft(3);
		// piCout << "check ignore" << lname << ignore_libs;
		if (ignore_libs.contains(lname)) {
			continue;
		}
		checkQtLib(fi.name());
		cur_libs << l;
		all_libs << l;
	}
	PIVector<PIString> clibs = cur_libs.toVector();
	if (!clibs.isEmpty()) piCout << " new dependencies:\n -" << PIStringList(clibs).join("\n - ");
	for (const auto & l: clibs) {
		procLdd(l, false, cur_depth);
	}
}

void copyWildcard(const PIString & from, const PIString & to) {
	piCout << "copy" << from;
	if (fake) return;
	PIDir(to).make();
#ifdef WINDOWS
	PIFile::FileInfo fi(from);
	system(("robocopy \"" + fi.dir() + "\" \"" + to + "\" \"" + fi.name() + "\" /E /NJH /NJS /NP /NDL /NS /NC /NFL 1> NUL").data());
#else
	auto _r        = system((cmd_copy + from + " \"" + to + "/\"" + cmd_suffix).data());
	NO_UNUSED(_r);
#endif
}


void procQt() {
	// piCout << "qmake ...";
	PIString vs;
	if (qt_dir.isEmpty()) {
		vs = execute("qmake -v");
		if (vs.isEmpty()) {
			piCout << "Can`t exec \"qmake -v\"!";
			return;
		}
	} else
		vs = "QMake version ?.?\nUsing Qt version ?.?.? in " + qt_dir;
	PIStringList vsl   = vs.split("\n");
	PIStringList pdirs = qt_plugins.toVector();
	if (!fake) {
		PIDir(qt_plugins_dir).make(true);
		PIDir(qt_qml_dir).make(true);
	}
	for (auto l: vsl) {
		if (l.trim().contains("Qt version")) {
			l.cutLeft(l.find("Qt version") + 10).trim();
			PIString qv = l.takeWord();
			l.takeWord();
			PIString qloc = l.trim();
			piCout << "Qt" << qv << "in" << qloc;
			PIString qdir;
			PIStringList suffixes({".", "..", "qt5", "../qt5", "qt6", "../qt6"});
			for (const auto & s: suffixes) {
				PIString qd = qloc + "/" + s + "/plugins/";
				if (piDebug) PICout(AddSpaces) << "Qt plugins root try" << qd << "...";
				if (PIDir::isExists(qd + "platforms")) {
					qdir = qloc + "/" + s + "/";
					piCout << " yes";
					break;
				}
				piCout << " no";
			}
			if (qdir.isEmpty()) break;
			for (const auto & plugin: pdirs) {
				PIStringList filters = qt_filters[plugin];
				piCout << "PLUG" << plugin << filters;
				for (const auto & f: filters) {
					if (f.isEmpty()) continue;
					copyWildcard(qdir + "plugins/" + plugin + "/" + f, qt_plugins_dir + plugin);
					PIVector<PIFile::FileInfo> copied = PIDir(qt_plugins_dir + plugin).entries();
					for (const auto & fi: copied) {
						if (fi.isFile()) {
							procLdd(fi.path);
							plugin_libs << fi.path;
						}
					}
				}
			}
			for (const auto & q: qml_list) {
				if (q.isEmpty()) continue;
				copyWildcard(qdir + "qml/" + q + "/*", qt_qml_dir + q);
				PIVector<PIFile::FileInfo> copied = PIDir(qt_qml_dir + q).allEntries();
				for (const auto & fi: copied) {
					if (fi.isFile()) {
						procLdd(fi.path);
						plugin_libs << fi.path;
					}
				}
			}
			break;
		}
	}
}


bool procDpkg(const PIString & l) {
	PIString dpkgdir;
	if (!dpkg_workdir.isEmpty()) dpkgdir = " --admindir=" + dpkg_workdir;
	PIFile::FileInfo fi;
	fi.path      = l;
	PIString cmd = dpkg + dpkgdir + " -S " + fi.name() + ign_err_suffix;
	// PICout(true) << "** dpkg:" << cmd;
	PIString vs  = execute(cmd).trim();
	if (!vs.isEmpty()) {
		PIStringList lines = vs.split('\n').reverse();
		for (auto l: lines) {
			l = l.left(l.find(":"));
			if (!l.isEmpty() && !l.contains(' ') && !l.endsWith("-cross") && !l.endsWith("-dev")) {
				// PICout(true) << "** found \"" << l << "\" in \"" << sl << "\"";
				all_deps << l;
				return true;
			}
		}
		for (auto l: lines) {
			l = l.left(l.find(":"));
			if (!l.isEmpty() && !l.contains(' ') && !l.endsWith("-cross")) {
				// PICout(true) << "** found \"" << l << "\" in \"" << sl << "\"";
				all_deps << l;
				return true;
			}
		}
	}
	// piCout << "No dep on" << l;
	return false;
}


bool procRpm(const PIString & l) {
	PIString rpmdir;
	if (!rpm_rootdir.isEmpty()) rpmdir = " --root " + rpm_rootdir;
	PIFile::FileInfo fi;
	fi.path      = l;
	PIString cmd = rpm_tool + rpmdir + " -qf " + fi.path + ign_err_suffix;
	// PICout(true) << "** rpm:" << cmd;
	PIString vs  = execute(cmd).trim();
	if (!vs.isEmpty()) {
		PIStringList lines = vs.split('\n');
		for (auto line: lines) {
			line.trim();
			if (line.isEmpty()) continue;
			// Filter out -devel, -debuginfo, -static packages
			if (line.contains("-devel") || line.contains("-debuginfo") || line.contains("-static")) continue;
			// Extract package name: "name-version-release.arch" → "name"
			// Find first hyphen that's not part of version
			int verStart = -1;
			for (int i = 0; i < line.size_s(); ++i) {
				if (line[i] == '-' && i + 1 < line.size_s() && line[i + 1].isDigit()) {
					verStart = i;
					break;
				}
			}
			if (verStart > 0) {
				PIString pkgName = line.left(verStart);
				// PICout(true) << "** found \"" << pkgName << "\" for \"" << l << "\"";
				all_deps << pkgName;
				return true;
			}
		}
	}
	// piCout << "No rpm dep on" << l;
	return false;
}


void patchNameTool() {
	if (nametool.isEmpty()) return;
	PIStringList clibs = all_libs.toVector(), flibs = framework_libs.toVector(), patch_list;
	PIStringList dlibs;
	PIString libname, cmd;
	// PICout(DefaultControls) << "start patch" << clibs;
	PIFile::FileInfo fi;
	patch_list = input_files;
	patch_list << plugin_libs;
	for (const auto & l: clibs) {
		fi.path = l;
		patch_list << (out_dir + fi.name());
	}
	for (const auto & local_lib: patch_list) {
		execute("chmod +w \"" + local_lib + "\"");
		fi.path = local_lib;
		cmd     = nametool + " -id \"@executable_path/../Frameworks/" + fi.name() + "\"";
		cmd += " \"" + local_lib + "\"" + ign_err_suffix;
		// piCout << " " << cmd;
		execute(cmd);
	}
	for (const auto & f: flibs) {
		PIString fl = findLib(frameworkName(f));
		if (fl.isEmpty()) continue;
		patch_list << (out_dir + frameworkName(f) + "/" + frameworkInternalPath(f));
		// PICout(DefaultControls) << "map" << f << "->" << (out_dir + frameworkName(f) + "/" + frameworkInternalPath(f));
	}
	for (const auto & local_lib: patch_list) {
		dlibs = filter(execute(otool + " -L \"" + local_lib + "\""), "(");
		for (auto & l: dlibs) {
			l = l.left(l.find('('));
			l.trim();
		}
		if (!dlibs.isEmpty()) {
			execute("chmod +w \"" + local_lib + "\"");
		}
		piCout << "patch" << local_lib;
		for (auto sys_lib: dlibs) {
			sys_lib.cutRight(1).trim();
			fi.path     = sys_lib;
			libname     = fi.name();
			PIString fl = findLib(libname), fname = frameworkName(sys_lib);
			// piCout << "  check" << sys_lib << fl;
			PIString new_path;
			if (all_libs.contains(fl) || all_libs.contains(sys_lib)) {
				new_path = "@executable_path/../Frameworks/" + libname;
				piCout << "  depend on lib" << (fl.isEmpty() ? sys_lib : fl);
			} else {
				if (frameworks.contains(fname)) {
					fl = findLib(fname);
					if (fl.isEmpty()) continue;
					new_path = "@executable_path/../Frameworks/" + fname + "/" + frameworkInternalPath(sys_lib);
					piCout << "  depend on framework" << fl;
				}
			}
			if (!new_path.isEmpty() && (sys_lib != new_path)) {
				cmd = nametool + " -change \"" + sys_lib + "\"";
				cmd += " \"" + new_path + "\"";
				cmd += " \"" + local_lib + "\"" + ign_err_suffix;
				// piCout << "  *" << cmd;
				execute(cmd);
			}
		}
	}
	for (const auto & bin: input_files) {
		cmd = nametool + " -add_rpath \"@executable_path/../Frameworks\"";
		cmd += " \"" + bin + "\"" + ign_err_suffix;
		execute(cmd);
	}
}


void patchRPathFile(const PIFile::FileInfo & file) {
	PIString fo = execute("file \"" + file.path + "\"");
	if (!fo.contains("ELF")) return;
	PIString rp  = "\\$ORIGIN:\\$ORIGIN/lib";
	PIString arp = PIDir(file.dir()).relative(out_dir);
	if (!arp.isEmpty() && arp != "." && arp != "lib") rp.append(":\\$ORIGIN/" + arp);
	PIString cmd = "patchelf --set-rpath \"" + rp + "\" \"" + file.path + "\"" + ign_err_suffix;
	piCout << "set rpath" << file.path << "to" << rp;
	execute(cmd);
}


void patchRPath() {
	PIStringList dirs({out_dir, target_dir, qt_plugins_dir, qt_qml_dir});
	dirs.removeDuplicates().removeStrings(PIString());
	for (const auto & d: dirs) {
		PIVector<PIFile::FileInfo> files = PIDir(d).allEntries();
		for (const auto & f: files) {
			if (f.isDir()) continue;
			patchRPathFile(f.path);
		}
	}
}


int main(int argc, char * argv[]) {
	PICLI cli(argc, argv);
	// piCout << cli.rawArguments();
	cli.setOptionalArgumentsCount(-1);
	cli.addArgument("help");
	cli.addArgument("verbose");
	cli.addArgument("Conf");
	cli.addArgument("fake");
	cli.addArgument("dependencies", PIChar('\0'));
	cli.addArgument("prefix", PIChar('\0'), true);
	cli.addArgument("output", true);
	cli.addArgument("search_path", true);
	cli.addArgument("ignore", PIChar('\0'), true);
	cli.addArgument("Styles", true);
	cli.addArgument("Platforms", true);
	cli.addArgument("qt-plugins", PIChar('\0'), true);
	cli.addArgument("qt-modules", PIChar('\0'), true);
	cli.addArgument("qml-modules", PIChar('\0'), true);
	cli.addArgument("qt-conf-dir", PIChar('\0'), true);
	cli.addArgument("qt-plugins-dir", PIChar('\0'), true);
	cli.addArgument("qt-qml-dir", PIChar('\0'), true);
	cli.addArgument("ldd", true);
	cli.addArgument("Lreadelf", true);
	cli.addArgument("Wobjdump", true);
	cli.addArgument("Motool", true);
	cli.addArgument("name-tool", PIChar('\0'), true);
	cli.addArgument("rpath", PIChar('\0'));
	cli.addArgument("strip", PIChar('\0'), true);
	cli.addArgument("no-strip", PIChar('\0'), false);
	cli.addArgument("Dpkg", true);
	cli.addArgument("dpkg-workdir", PIChar('\0'), true);
	cli.addArgument("Rpm", true);
	cli.addArgument("rpm-rootdir", PIChar('\0'), true);
	cli.addArgument("depth", true);
	cli.addArgument("qtdir", true);
	cli.addArgument("add_libs", true);
	if (cli.hasArgument("help") || cli.argumentValue("output").isEmpty() || cli.optionalArguments().isEmpty()) {
		usage();
		return 0;
	}
	setCommands();

	fake        = cli.hasArgument("fake");
	piDebug     = cli.hasArgument("verbose");
	is_deps     = cli.hasArgument("dependencies");
	out_dir     = cli.argumentValue("output");
	lib_dirs    = cli.argumentValue("search_path").split(DELIM).removeAll("");
	add_libs    = cli.argumentValue("add_libs").split(DELIM).removeAll("");
	ignore_libs = PISet<PIString>(cli.argumentValue("ignore").split(DELIM).removeAll(""));
	qt_dir      = cli.argumentValue("qtdir");
	ldd         = cli.argumentValue("ldd");
	readelf     = cli.argumentValue("Lreadelf");
	objdump     = cli.argumentValue("Wobjdump");
	otool       = cli.argumentValue("Motool");
	nametool    = cli.argumentValue("name-tool");
	strip       = cli.argumentValue("strip");
	rpath       = cli.hasArgument("rpath");
	if (nametool.isEmpty()) nametool = "install_name_tool";
	if (strip.isEmpty()) strip = "strip";
	if (cli.hasArgument("no-strip")) strip.clear();
	dpkg         = cli.argumentValue("Dpkg");
	dpkg_workdir = cli.argumentValue("dpkg-workdir");
	rpm_tool     = cli.argumentValue("Rpm");
	rpm_rootdir  = cli.argumentValue("rpm-rootdir");
	if (!rpm_tool.isEmpty()) use_rpm = true;
#ifdef WINDOWS
	readelf.replaceAll("/", "\\");
	objdump.replaceAll("/", "\\");
	otool.replaceAll("/", "\\");
	nametool.replaceAll("/", "\\");
	dpkg.replaceAll("/", "\\");
	rpm_tool.replaceAll("/", "\\");
#endif
	if (dpkg.isEmpty()) dpkg = "/usr/bin/dpkg";
	if (rpm_tool.isEmpty()) rpm_tool = "/usr/bin/rpm";
	int etcnt = 0;
	if (!readelf.isEmpty()) ++etcnt;
	if (!objdump.isEmpty()) ++etcnt;
	if (!otool.isEmpty()) ++etcnt;
	if (etcnt > 1) {
		piCerr << "Can use only one of \"readelf\", \"objdump\" and \"otool\"!";
		return 1;
	}
	if (etcnt > 0) is_ldd = false;
	if (!qt_dir.isEmpty()) {
		PIString qroot = qt_dir;
		if (!qroot.endsWith("/")) qroot.append("/");
		lib_dirs << (qroot + "bin") << (qroot + "lib") << (qroot + "Frameworks");
	}
	for (auto & s: lib_dirs) {
		s.trim();
		if (!s.endsWith("/")) s += "/";
	}
	if (out_dir.isEmpty()) out_dir = ".";
	if (!out_dir.endsWith("/")) out_dir += "/";
	if (ldd.isEmpty()) ldd = "/usr/bin/ldd";

	out_dir.replaceAll("//", "/");

	for (int i = 0;; ++i) {
		if (qt_deps[i].lib.isEmpty()) break;
		qt_deps[i].plugins.forEach([](const PIString & p) { qt_filters[p] = PIStringList("*"); });
	}
	if (!cli.argumentValue("Platforms").isEmpty()) qplatforms = cli.argumentValue("Platforms");
	if (platforms.contains("windows")) win_target = true;
	platforms = qplatforms.split(",");
	styles    = cli.argumentValue("Styles").split(",");
	if (styles.isEmpty()) styles << "";
	PIStringList qpd = cli.argumentValue("qt-plugins").toLowerCase().split(DELIM).removeAll("");
	for (const auto & qp: qpd) {
		int _i = qp.find('=');
		if (_i < 0) continue;
		PIString pname     = qp.left(_i).trim();
		PIStringList pfilt = qp.mid(_i + 1).trim().split(",");
		if (pname == "*") {
			for (int i = 0;; ++i) {
				if (qt_deps[i].lib.isEmpty()) break;
				qt_deps[i].plugins.forEach([&pfilt](const PIString & p) { qt_filters[p] = pfilt; });
			}
		} else {
			qt_plugins << pname;
			qt_filters[pname] = pfilt;
		}
	}
	qt_filters["platforms"] = platforms;
	qt_filters["styles"]    = styles;

	qt_add_libs             = cli.argumentValue("qt-modules").split(DELIM).removeAll("");
	auto getQtSubdir        = [&cli](const char * arg, PIString & var) {
        var = cli.argumentValue(arg).trim();
        if (var.isEmpty())
            var = out_dir;
        else {
            if (!PIFile::FileInfo(var).isAbsolute()) var = out_dir + "/" + var;
        }
        if (!var.endsWith("/")) var += "/";
        var.replaceAll("//", "/");
	};
	getQtSubdir("qt-conf-dir", qt_conf_dir);
	getQtSubdir("qt-plugins-dir", qt_plugins_dir);
	getQtSubdir("qt-qml-dir", qt_qml_dir);

	qml_list = cli.argumentValue("qml-modules").split(DELIM).removeAll("");

	need_qt  = !qt_add_libs.isEmpty();

	auto it  = qt_filters.makeIterator();
	while (it.next())
		it.value().forEach([](PIString & i) {
			if (!i.startsWith("*")) i.prepend("*");
			if (!i.endsWith("*")) i.append("*");
		});
	// PICout(PICoutManipulators::DefaultControls) << qt_filters;

	if (!fake) PIDir(out_dir).make();
	if (!cli.argumentValue("depth").isEmpty()) depth = cli.argumentValue("depth").toInt();

	cli.optionalArguments().forEach([](const PIString & a) {
		if (PIDir::isExists(a)) {
			if (target_dir.isEmpty()) target_dir = a;
			PIDir(a).allEntries().forEach([](const PIFile::FileInfo & fi) {
				if (fi.isFile()) input_files << fi.path;
			});
		} else {
			if (target_dir.isEmpty()) target_dir = PIFile::FileInfo(a).dir();
			if (PIFile::isExists(a)) input_files << a;
		}
	});

	// piCout << files;
	if (depth > 0) {
		input_files.forEach([](const PIString & f) { procLdd(f); });
		qt_add_libs.forEach([](const PIString & f) {
			PIString l = findLib(qt_pref + f + qt_suff);
			if (l.isEmpty()) return;
			procLdd(l, true);
			checkQtLib(f);
		});
	}
	for (auto & s: add_libs) {
		if (s.isEmpty()) continue;
		PIString alib = findLib(s);
		if (alib.isEmpty()) continue;
		piCout << s << "->" << alib;
		procLdd(alib, true);
		if (!all_libs[alib]) all_libs << alib;
	}
	if (need_qt) {
		bool need_qt_plugins = true;
		if (is_deps) { // if Qt in system, then no plugins copy
			PIVector<PIString> qlibs = qt_libs.toVector();
			piCout << "check for installed Qt" << qlibs;
			for (const auto & l: qlibs) {
				if (use_rpm ? procRpm(l) : procDpkg(l)) {
					piCout << "system Qt found!";
					need_qt_plugins = false;
					break;
				}
			}
		}
		if (need_qt_plugins) {
			procQt();
			if (!ldd.isEmpty() || !readelf.isEmpty()) { // qt.conf for Linux and Windows
				PIFile qtc(qt_conf_dir + "qt.conf", PIIODevice::ReadWrite);
				qtc.clear();
				PIString pp = PIDir(qt_conf_dir).relative(qt_plugins_dir), qp = PIDir(qt_conf_dir).relative(qt_qml_dir), lp = "lang/";
				if (!pp.isEmpty() && !pp.endsWith('/')) pp.append('/');
				if (!qp.isEmpty() && !qp.endsWith('/')) qp.append('/');
				if (!otool.isEmpty()) {
					pp = "PlugIns";
					lp = "Resources/lang";
				}
				// piCout << pp;
				PIIOTextStream ts(&qtc);
				ts << "[Paths]\n\tPlugins = " << pp << "\n\tQml2Imports = " << qp << "\n\tTranslations = " << lp << "\n";
			}
		}
	}

#ifdef WINDOWS
	out_dir.replaceAll("/", "\\");
#endif
	PIVector<PIString> clibs = all_libs.toVector();
	static PIStringList ignore_libs({"libc.so"});
	for (auto l: clibs) {
		bool ignore_lib = false;
		for (auto il: ignore_libs) {
			if (l.startsWith(il)) {
				ignore_lib = true;
				break;
			}
		}
		if (ignore_lib) continue;
		PIFile::FileInfo fi;
		fi.path = l;
#ifdef WINDOWS
		l.replaceAll("/", "\\");
#endif
		bool need_cp = true;
		if (is_deps) need_cp = !(use_rpm ? procRpm(l) : procDpkg(l));
		if (need_cp) {
			piCout << "copy" << l;
			if (!fake) {
				auto _r = system((cmd_copy + "\"" + l + "\" \"" + out_dir + "\"" + cmd_suffix).data());
				if (strip.isNotEmpty()) {
					if (!otool.isEmpty()) // Apple
						_r = system((strip + " -S \"" + out_dir + fi.name() + "\"").data());
					else
						_r = system((strip + " --strip-unneeded \"" + out_dir + fi.name() + "\"").data());
				}
				NO_UNUSED(_r);
			}
		}
	}
	PIVector<PIString> fwdirs = frameworks.toVector();
	for (const auto & f: fwdirs) {
		PIString fd = findLib(f);
		if (!fd.isEmpty()) {
			piCout << "copy framework" << f;
			if (!fake) {
				auto _r = system((cmd_copydir + "\"" + fd + "\" \"" + out_dir + "\"" + cmd_suffix).data());
				NO_UNUSED(_r);
			}
		} else
			miss_frameworks << f;
	}

	if (!otool.isEmpty()) patchNameTool();

	if (rpath) patchRPath();

	if (is_deps) {
		PICout(PICoutManipulators::AddNone) << cli.argumentValue("prefix");
		PICout(PICoutManipulators::AddNewLine) << PIStringList(all_deps.toVector()).join(", ");
	} else {
		piCout << "copied" << clibs.size_s() << "files";
		if (!miss_libs.isEmpty()) piCout << "Missing libraries:\n -" << PIStringList(miss_libs.toVector()).join("\n - ");
		if (!miss_frameworks.isEmpty()) piCout << "Missing frameworks:\n -" << PIStringList(miss_frameworks.toVector()).join("\n - ");
	}

	return 0;
}
