package Common.Utils;
import Common.Constants;
import Common.Global;
import Common.UI.UI;
import Common.Utils.Files.VFileChooser_;
import GlobalData.Settings.SettingName;
import GlobalData.Tasks.TaskState;
import ProjectData.Files.DBProjectFile;
import ProjectData.Project.db_project_info;
import Visual_DVM_2021.Passes.PassException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import javafx.util.Pair;
import org.apache.commons.io.FileUtils;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.io.*;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.security.MessageDigest;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.*;
import java.util.concurrent.Semaphore;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class Utils {
    public static Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
    public static Object jsonFromFile(File file, Class json_class) throws Exception {
        String packed = FileUtils.readFileToString(file, Charset.defaultCharset());
        return Utils.gson.fromJson(packed, json_class);
    }
    public static void jsonToFile(Object json_object, File file) throws Exception {
        FileUtils.writeStringToFile(file, Utils.jsonToPrettyFormat(Utils.gson.toJson(json_object)));
    }
    //--->>
    public static String hideRegularMetasymbols(String word) {
        String res = word.replace("\\", "\\\\");
        for (char c : Constants.regular_metasymbols)
            res = res.replace(String.valueOf(c), "\\" + c);
        return res;
    }
    public static boolean isLinuxSystemCommand(String text) {
        for (String command : Constants.linux_system_commands) {
            if (text.equalsIgnoreCase(command)) return true;
        }
        return false;
    }
    public static boolean ContainsForbiddenName(String string) {
        char[] chars = string.toCharArray();
        for (char c : chars)
            if (isForbidden(c)) return true;
        return false;
    }
    public static boolean isForbidden(char c) {
        for (char f : Constants.forbidden_file_name_characters)
            if (c == f) return true;
        return false;
    }
    public static String ReplaceForbiddenSymbols(String name) {
        StringBuilder res = new StringBuilder();
        for (char c : name.toCharArray())
            res.append(isForbidden(c) ? '_' : c);
        return res.toString();
    }
    public static void init() {
        for (char f : Constants.forbidden_file_name_characters)
            Constants.all_forbidden_characters_string += f + " ";
    }
    public static String DQuotes(Object o) {
        return "\"" + o.toString() + "\"";
    }
    public static String Quotes(Object o) {
        return "'" + o.toString() + "'";
    }
    public static String Brackets(Object o) {
        return "[" + o.toString() + "]";
    }
    public static String Bold(Object o) {
        return "<b>" + o.toString() + "</b>";
    }
    public static String RBrackets(Object o) {
        return "(" + o.toString() + ")";
    }
    public static String MFVar(Object o) {
        return "$(" + o.toString() + ")";
    }
    public static String TBrackets(Object o) {
        return "<" + o.toString() + ">";
    }
    public static String getExtension(File file) {
        String fn = file.getName();
        int di = fn.lastIndexOf(".");
        return (di >= 0) ? fn.substring(di + 1).toLowerCase() : "";
    }
    public static String getExtensionByName(String fn) {
        ;
        int di = fn.lastIndexOf(".");
        return (di >= 0) ? fn.substring(di + 1).toLowerCase() : "";
    }
    public static String getFileNameWithoutExtension(File file) {
        return getNameWithoutExtension(file.getName());
    }
    public static String getNameWithoutExtension(String fn) {
        return (fn.contains(".")) ? fn.substring(0, fn.lastIndexOf(".")).toLowerCase() : fn.toLowerCase();
    }
    public static boolean ContainsCyrillic(String string) {
        return string.chars()
                .mapToObj(Character.UnicodeBlock::of)
                .anyMatch(b -> b.equals(Character.UnicodeBlock.CYRILLIC));
    }
    public static void CheckDirectory(File dir) {
        // System.out.println("check: "+dir);
        if (!dir.exists()) {
            try {
                FileUtils.forceMkdir(dir);
            } catch (Exception e) {
                Global.Log.PrintException(e);
            }
        }
    }
    public static void CheckAndCleanDirectory(File dir) {
        // System.out.println("check and clean: "+dir);
        if (dir.exists()) {
            File[] files = dir.listFiles();
            if (files != null)
                for (File f : files) {
                    try {
                        forceDeleteWithCheck(f);
                    } catch (Exception e) {
                        Global.Log.PrintException(e);
                    }
                }
        } else {
            try {
                FileUtils.forceMkdir(dir);
            } catch (Exception e) {
                Global.Log.PrintException(e);
            }
        }
    }
    public static Object requireNonNullElse(Object value, Object default_value) {
        return (value != null) ? value : default_value;
    }
    //https://javadevblog.com/kak-schitat-fajl-v-string-primer-chteniya-fajla-na-java.html
    //https://habr.com/ru/post/269667/
    public static String ReadAllText(File file) {
        try {
            return new String(Files.readAllBytes(file.toPath()));
        } catch (Exception e) {
            Global.Log.PrintException(e);
        }
        return "";
    }
    public static String toU(String path) {
        return path.replace('\\', '/');
    }
    public static String toW(String path) {
        return path.replace('/', '\\');
    }
    public static double getFileSizeMegaBytes(File file) {
        double res = file.length() / (1024 * 1024);
        System.out.println(res);
        return res;
    }
    public static void CleanDirectory(File dir) {
        if (dir.exists() && dir.isDirectory()) {
            File[] files = dir.listFiles();
            if (files != null) {
                for (File f : files) {
                    try {
                        forceDeleteWithCheck(f);
                    } catch (Exception e) {
                        Global.Log.PrintException(e);
                    }
                }
            }
        }
    }
    public static long last_ticks = Constants.Nan;
    public static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (Exception ignore) {
        }
    }
    public static Semaphore date_semaphore = new Semaphore(1);
    public static long getDateNumber() {
        //-
        try {
            date_semaphore.acquire();
        } catch (Exception ignore) {
        }
        //-
        long ticks = new Date().toInstant().getEpochSecond();
        while (ticks == last_ticks) {
            sleep(1);
            ticks = new Date().toInstant().getEpochSecond();
        }
        last_ticks = ticks;  // Это и есть разделяемый ресурс.
        date_semaphore.release();
        return ticks;
    }
    public static String getDateName(String name_) {
        return name_ + "_" + getDateNumber();
    }
    public static void WriteToFile(File f, String text) throws IOException {
        Files.write(
                f.toPath(),
                text.getBytes(),
                StandardOpenOption.CREATE,
                StandardOpenOption.TRUNCATE_EXISTING);
    }
    public static boolean isDigit(String s) {
        try {
            Integer.parseInt(s);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }
    public static boolean isEnglishLetter(char c) {
        return (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')));
    }
    public static boolean isRussianLetter(char c) {
        return ((c >= 'а') && (c <= 'я'))
                || ((c >= 'А') && (c <= 'Я'))
                || (c == 'Ё')
                || (c == 'ё');
    }
    public static boolean isSign(char c) {
        switch (c) {
            //арифметика.
            case '+':
            case '-':
            case '*':
            case '/':
            case '<':
            case '>':
            case '&':
            case '=':
            case '%':
            case '^':
                //- обр слеш
            case '\\':
                //препинание
            case ' ':
            case '_':
            case '.':
            case ',':
            case '!':
            case '?':
            case ';':
            case ':':
                //escape последовательности
            case '\t':
            case '\n':
            case '\r':
                //кавычки
            case '\'':
            case '"':
                //- скобки
            case '(':
            case ')':
            case '[':
            case ']':
            case '{':
            case '}':
                //прочее
            case '~':
            case '`':
            case '|':
            case '@':
            case '$':
            case '#':
            case '№':
                return true;
        }
        return false;
    }
    public static char Translit(char c) {
        switch (c) {
            case 'А':
            case 'а':
            case 'Я':
            case 'я':
                return 'A';
            //
            case 'Б':
            case 'б':
                return 'B';
            //-
            case 'В':
            case 'в':
                return 'V';
            //
            case 'Г':
            case 'г':
                return 'G';
            //
            case 'Д':
            case 'д':
                return 'D';
            //
            case 'Е':
            case 'е':
            case 'Ё':
            case 'ё':
            case 'Э':
            case 'э':
                return 'E';
            //
            case 'Ж':
            case 'ж':
                return 'J';
            //
            case 'З':
            case 'з':
                return 'Z';
            //
            case 'И':
            case 'и':
            case 'Й':
            case 'й':
                return 'I';
            //
            case 'К':
            case 'к':
                return 'K';
            //
            case 'Л':
            case 'л':
                return 'L';
            //
            case 'М':
            case 'м':
                return 'M';
            //
            case 'Н':
            case 'н':
                return 'N';
            //
            case 'О':
            case 'о':
                return 'O';
            //
            case 'П':
            case 'п':
                return 'P';
            //
            case 'Р':
            case 'р':
                return 'R';
            //
            case 'С':
            case 'с':
                return 'S';
            case 'Т':
            case 'т':
                return 'T';
            //
            case 'У':
            case 'у':
            case 'Ю':
            case 'ю':
                return 'U';
            case 'Х':
            case 'х':
            case 'Щ':
            case 'щ':
            case 'Ш':
            case 'ш':
                return 'H';
            //
            case 'Ф':
            case 'ф':
                return 'F';
            //
            case 'Ч':
            case 'ч':
            case 'Ц':
            case 'ц':
                return 'C';
            //
            case 'Ы':
            case 'ы':
                return 'Y';
            //
        }
        return ' ';
    }
    public static String ending(boolean flag) {
        return flag ? ")" : ",";
    }
    // http://java-online.ru/blog-archive.xhtml
    public static void getFilesCountR(File dir, Index res) {
        for (File f : dir.listFiles()) {
            res.Inc();
            if (f.isDirectory())
                getFilesCountR(f, res);
        }
    }
    public static int getFilesCount(File dir) {
        Index res = new Index();
        getFilesCountR(dir, res);
        return res.getValue();
    }
    public static String print_date(Date date) {
        String pattern = "dd.MM.yyyy HH:mm:ss";
        DateFormat df = new SimpleDateFormat(pattern);
        return df.format(date);
    }
    public static boolean isBracketsBalanced(String fragment) {
        int cc = 0;
        for (char c : fragment.toCharArray()) {
            if (c == '(')
                cc++;
            if (c == ')')
                cc--;
            if (cc < 0)
                return false;
        }
        return (cc == 0);
    }
    public static File CreateTempResourceFile(String res_name) throws Exception {
        URL u = (Utils.class.getResource("/files/" + res_name));
        InputStream i = u.openStream();
        Path p = Paths.get(Global.TempDirectory.getAbsolutePath(), getDateName(res_name));
        Files.copy(i, p, StandardCopyOption.REPLACE_EXISTING);
        return p.toFile();
    }
    public static void CreateResourceFile(String res_name, File dst) throws Exception {
        URL u = (Utils.class.getResource("/files/" + res_name));
        InputStream i = u.openStream();
        Files.copy(i, dst.toPath(), StandardCopyOption.REPLACE_EXISTING);
    }
    //просто дать объект под файл с сгенерированным именем
    public static File CreateTempFile(String name, String text) throws IOException {
        File res = getTempFileName(name);
        WriteToFile(res, text);
        return res;
    }
    public static File CreateTempFile(String name, String ext, String text) throws IOException {
        File res = getTempFileName(name, ext);
        WriteToFile(res, text);
        return res;
    }
    public static File getTempFileName(String name, String ext) {
        return Paths.get(System.getProperty("user.dir"), "Temp", getDateName(name) + (ext.isEmpty() ? "" : ("." + ext))).toFile();
    }
    public static File getTempFileName(String name) {
        return getTempFileName(name, "");
    }
    public static boolean isAnchestor(File child, File anchestor) {
        return child.getAbsolutePath().startsWith(anchestor.getAbsolutePath() + (Global.isWindows ? "\\" : "/"));
    }
    //при условии что это точно его предок
    public static String getRelativeAddress(File file, File anchestor) {
        return file.getAbsolutePath().substring(anchestor.getAbsolutePath().length() + 1);
    }
    public static String pack(String s_in) {
        return String.join(" ",
                Arrays.stream(s_in.split(" ")).filter(d -> !d.isEmpty()).collect(Collectors.toCollection(Vector::new)));
    }
    public static String remove(String string, String... to_remove) {
        String res = string;
        for (String c : to_remove)
            res = res.replace(c, "");
        return res;
    }
    //для переименования/добавления новых файлов.
    public static boolean validateFileShortNewName(String name, TextLog Log) {
        boolean res = true;
        if (name.isEmpty()) {
            Log.Writeln_("Имя файла не может быть пустым");
            res = false;
        }
        if (ContainsCyrillic(name)) {
            Log.Writeln_("Имя файла не может содержать кириллицу");
            res = false;
        }
        if (ContainsForbiddenName(name)) {
            Log.Writeln_("Имя файла не может содержать запрещённых символов\n" + Constants.all_forbidden_characters_string);
            res = false;
        }
        return res;
    }
    //корень сюда гарантированно не попадает. значит можно запрещать двоеточие.
    //идет по всем уровням файлов
    public static boolean validateProjectFile(File file, TextLog Log) {
        String name = file.getName();
        if (ContainsCyrillic(name) || ContainsForbiddenName(name)) {
            Log.Writeln_(file.getAbsolutePath());
            return false;
        }
        return true;
    }
    //входной параметр всегда папка.
    public static void validateProjectFolder_r(File dir, TextLog Log) {
        validateProjectFile(dir, Log);
        File[] files = dir.listFiles();
        if (files != null)
            for (File file : files) {
                if (file.isDirectory())
                    validateProjectFolder_r(file, Log);
                else validateProjectFile(file, Log);
            }
    }
    public static boolean validateProjectFolder(File dir, TextLog Log) {
        TextLog files_list = new TextLog();
        String[] filesLines = files_list.text.split("\n");
        validateProjectFolder_r(dir, files_list);
        if (!files_list.isEmpty())
            Log.Writeln_("Имена " + filesLines.length + " файлов/подпапок содержат запрещённые символы " +
                    Constants.all_forbidden_characters_string +
                    "или кириллицу");
        //нужно проверить корень на наличие хоть одной программы.
        return Log.isEmpty();
    }
    public static ImageIcon getIcon(String path) {
        URL imageUrl = Utils.class.getResource(path);
        if (imageUrl == null) {
            System.out.println("image: " + Brackets(path) + "not found");
            return null;
        }
        return new ImageIcon(imageUrl);
    }
    public static ImageIcon getTabIcon(String path) {
        URL imageUrl = Utils.class.getResource(path);
        if (imageUrl == null) {
            System.out.println("image: " + Brackets(path) + "not found");
            return null;
        }
        ImageIcon icon = new ImageIcon(imageUrl);
        return new ImageIcon(icon.getImage().getScaledInstance(
                18,
                18,
                Image.SCALE_DEFAULT));
    }
    public static void CopyToClipboard(String text) {
        Toolkit.getDefaultToolkit()
                .getSystemClipboard()
                .setContents(
                        new StringSelection(text),
                        null
                );
    }
    public static String getFromClipboard() {
        String res = "";
        try {
            res = (String) Toolkit.getDefaultToolkit()
                    .getSystemClipboard().getData(DataFlavor.stringFlavor);
        } catch (Exception ex) {
            Global.Log.PrintException(ex);
        }
        return res;
    }
    public static void delete_with_check(File file) throws Exception {
        int attempts = 0;
        while (attempts < 10) {
            if (file.exists()) {
                try {
                    FileUtils.forceDelete(file);
                } catch (Exception ex) {
                    //   ex.printStackTrace();
                }
            } else return;
            if (file.exists()) {
                attempts++;
                System.out.println("файл " + Brackets(file.getAbsolutePath()) + " занят");
                Thread.sleep(2000);
            } else return;
        }
        throw new PassException("Не удалось удалить файл " + Brackets(file.getAbsolutePath()) + " за " + attempts + " попыток");
    }
    public static void GetVertices(float R, float r, float x0, float y0, int n, float phi) {
        boolean inner = false;
        for (int i = 0; i < 2 * n; i++) {
            float rad = inner ? r : R;
            System.out.println("x=" + (x0 + rad * Math.cos(phi + Math.PI * i / n)));
            System.out.println("y=" + (y0 + rad * Math.sin(phi + Math.PI * i / n)));
            System.out.println("--");
            inner = !inner;
        }
    }
    public static String md5Custom(String st) {
        MessageDigest messageDigest = null;
        byte[] digest = new byte[0];
        try {
            messageDigest = MessageDigest.getInstance("MD5");
            messageDigest.reset();
            messageDigest.update(st.getBytes());
            digest = messageDigest.digest();
        } catch (Exception e) {
            // тут можно обработать ошибку
            // возникает она если в передаваемый алгоритм в getInstance(,,,) не существует
            Global.Log.PrintException(e);
        }
        BigInteger bigInt = new BigInteger(1, digest);
        String md5Hex = bigInt.toString(16);
        while (md5Hex.length() < 32) {
            md5Hex = "0" + md5Hex;
        }
        return md5Hex;
    }
    public static void renameSubdirs_r(DefaultMutableTreeNode node, File old_anchestor, File new_anchestor) {
        if (node.getUserObject() instanceof File) {
            File old_file = (File) node.getUserObject();
            String relative_name = old_file.getAbsolutePath().substring(old_anchestor.getAbsolutePath().length() + 1);
            File new_file = Paths.get(new_anchestor.getAbsolutePath(), relative_name).toFile();
            node.setUserObject(new_file);
            for (int i = 0; i < node.getChildCount(); ++i) {
                renameSubdirs_r((DefaultMutableTreeNode) node.getChildAt(i), old_anchestor, new_anchestor);
            }
        }
    }
    public static void forceDeleteWithCheck(File file) throws Exception {
        int attempts = 0;
        while (attempts < 10) {
            if (file.exists()) {
                try {
                    FileUtils.forceDelete(file);
                } catch (Exception ignore) {
                }
            } else return;
            if (file.exists()) {
                attempts++;
                Global.Log.Print("неудачная попытка удаления: файл " + Brackets(file.getAbsolutePath()) + " занят");
                Thread.sleep(2000);
            } else return;
        }
        throw new PassException("Не удалось удалить файл " + Brackets(file.getAbsolutePath()) + " за " + attempts + " попыток");
    }
    public static byte[] packFile(File src) throws Exception {
        System.out.println("pack begins: " + src.getAbsolutePath());
        byte[] dst = Files.readAllBytes(src.toPath());
        System.out.println("pack done: bytes:" + dst.length);
        return dst;
    }
    public static void unpackFile(byte[] bytes, File dst) throws Exception {
        FileOutputStream os = new FileOutputStream(dst);
        System.out.println(dst.getAbsolutePath());
        System.out.println("unpack begins: bytes:" + bytes.length);
        os.write(bytes);
        os.close();
        System.out.println("unpack done to " + dst.getAbsolutePath());
    }
    public static Socket createClientSocket(InetAddress address, int port, int timeout) throws Exception {
        Socket socket = new Socket();
        socket.setSoTimeout(timeout);
        socket.connect(new InetSocketAddress(address, port), timeout);
        return socket;
    }
    public static void CreateResourceFile(File dst) throws Exception {
        URL u = (Utils.class.getResource("/files/" + dst.getName()));
        InputStream i = u.openStream();
        Files.copy(i, dst.toPath(), StandardCopyOption.REPLACE_EXISTING);
    }
    public static boolean validateEmail(String address, TextLog log) {
        Matcher matcher = Constants.VALID_EMAIL_ADDRESS_REGEX.matcher(address);
        if (!matcher.find()) {
            log.Writeln_("введённый адрес электронной почты некорректен.");
            return false;
        }
        String match = address.substring(matcher.start(), matcher.end());
        if (!match.equals(address)) {
            log.Writeln_("введённый адрес электронной почты некорректен.");
            return false;
        }
        return true;
    }
    public static void getFilesByExtensions_r(File dir, Vector<File> res, String... extensions) {
        File[] files = dir.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isFile()) {
                    String file_extension = getExtension(file);
                    for (String ext : extensions) {
                        if (file_extension.equalsIgnoreCase(ext)) {
                            res.add(file);
                            //  System.out.println(file.getAbsolutePath() + ":" + Utils.Brackets(file_extension));
                            //  System.out.println("MATCH");
                        }
                    }
                }
                if (file.isDirectory())
                    getFilesByExtensions_r(file, res, extensions);
            }
        }
    }
    //----->>
    public static void deleteFilesByExtensions(File dir, String... extensions) throws Exception {
        Vector<File> res = new Vector<>();
        Utils.getFilesByExtensions_r(dir, res, extensions);
        for (File src : res)
            Utils.forceDeleteWithCheck(src);
    }
    //----->>
    //--процессы-------------------------------------------------->>>>>
    //<editor-fold desc="создание скрипта">
    public static Process startScript(File scriptDirectory, File targetDirectory, String name, String scriptText, Map<String, String> envs) throws Exception {
        //->
        File scriptFile = Paths.get(scriptDirectory.getAbsolutePath(), name + (Global.isWindows ? ".bat" : "")).toFile();
        FileUtils.write(scriptFile, "cd " + Utils.DQuotes(targetDirectory.getAbsolutePath()) + "\n" + scriptText);
        if (!scriptFile.setExecutable(true)) throw new PassException("Не удалось создать исполняемый файл для скрипта");
        //->>
        ProcessBuilder procBuilder = new ProcessBuilder(scriptFile.getAbsolutePath());
        procBuilder.directory(scriptDirectory);
        procBuilder.redirectErrorStream(true);
        if (envs != null) {
            for (String envName : envs.keySet()) {
                procBuilder.environment().put(envName, envs.get(envName));
            }
        }
        return procBuilder.start();
    }
    public static Process startScript(File scriptDirectory, File targetDirectory, String name, String scriptText) throws Exception {
        return startScript(scriptDirectory, targetDirectory, name, scriptText, null);
    }
    public static Process createScript(File scriptDirectory, File targetDirectory, String name, String scriptText) throws Exception {
        //->
        File scriptFile = Paths.get(scriptDirectory.getAbsolutePath(), name + (Global.isWindows ? ".bat" : "")).toFile();
        FileUtils.write(scriptFile, "cd " + Utils.DQuotes(targetDirectory.getAbsolutePath()) + "\n" + scriptText);
        if (!scriptFile.setExecutable(true)) throw new PassException("Не удалось создать исполняемый файл для скрипта");
        //->>
        ProcessBuilder procBuilder = new ProcessBuilder(scriptFile.getAbsolutePath());
        procBuilder.directory(scriptDirectory);
        procBuilder.redirectErrorStream(true);
        return null;
    }
    //</editor-fold>
    //<editor-fold desc="чтение вывода процесса">
    public static String readLine(Process process) throws Exception {
        InputStream stdout = process.getInputStream();
        InputStreamReader isrStdout = new InputStreamReader(stdout);
        BufferedReader brStdout = new BufferedReader(isrStdout);
        String line = brStdout.readLine();
        System.out.println(line);
        return line;
    }
    public static Vector<String> readAllLines(Process process) throws Exception {
        Vector<String> output = new Vector<>();
        InputStream stdout = process.getInputStream();
        InputStreamReader isrStdout = new InputStreamReader(stdout);
        BufferedReader brStdout = new BufferedReader(isrStdout);
        String line;
        while ((line = brStdout.readLine()) != null) {
            System.out.println(line);
            output.add(line);
        }
        return output;
    }
    public static String readAllOutput(Process process) throws Exception {
        return String.join("\n", readAllLines(process));
    }
    //</editor-fold>
    public static String extractHeaderName(String line) {
        String tline = line.trim().toLowerCase();
        if (tline.startsWith("include")) {
            String[] data = tline.split("'");
            return data.length > 1 ? data[1] : null;
        }
        return null;
    }
    public static String getRelativeName(File dir, File file) {
        return
                file.getAbsolutePath().startsWith(dir.getAbsolutePath()) ?
                        file.getAbsolutePath().substring(dir.getAbsolutePath().length() + 1).replace('/', '\\') :
                        file.getAbsolutePath()
                ;
    }
    public static String compareTexts(String master, String slave) {
        Vector<String> lines = new Vector<>(Arrays.asList(master.split("\n")));
        Vector<String> slavelines = new Vector<>(Arrays.asList(slave.split("\n")));
        Vector<String> t2 = new Vector<>();
        int old_j = 0;
        int j = 0;
        for (int i = 0; i < lines.size(); ++i) {
            if (UI.Contains(slavelines, lines.get(i), old_j)) {
                for (int k = old_j; k < slavelines.size(); ++k) {
                    j = k;
                    if (lines.get(i).equals(slavelines.get(k))) {
                        j++;
                        t2.add(slavelines.get(k));
                        break;
                    } else
                        t2.add("+ " + slavelines.get(k));
                }
                old_j = j;
            } else {
                //строки гарантированно нет.
                t2.add("- " + lines.get(i));
            }
        }
        //теперь граничное условие. если первый файл кончился а второй нет, его остаток это добавление.
        for (int i = j; i < slavelines.size(); ++i)
            t2.add("+ " + slavelines.get(i));
        return String.join("\n", t2);
    }
    public static int getHalfKernels() {
        int countCores = 1;
        if (Runtime.getRuntime().availableProcessors() > 1)
            countCores = Runtime.getRuntime().availableProcessors() / 2;
        return countCores;
    }
    public static int getMaxKernels() {
        return Math.max(2, Runtime.getRuntime().availableProcessors());
    }
    public static int getMatrixProcessors(String matrix) {
        if (matrix.trim().isEmpty()) return 1;
        String[] data = matrix.split(" ");
        int res = 1;
        for (String d : data)
            res *= Integer.parseInt(d);
        return res;
    }
    public static int getCTestMaxDim(File test) {
        int fileMax = 0;
        final String prefix = "#pragma dvm array distribute";
        int n = 0;
        try {
            for (String line : FileUtils.readLines(test, Charset.defaultCharset())) {
                //  #pragma dvm array distribute[block][block], не важно
                String packedLine = remove(pack(line).toLowerCase(), "\n", "\r", "\t");
                if (packedLine.startsWith(prefix)) {
                    // System.out.println(n + ": " + packedLine);
                    packedLine = packedLine.substring(prefix.length());
                    boolean bracketOpen = false;
                    int pragmaMax = 0;
                    String distr = "";
                    for (int i = packedLine.indexOf('['); i < packedLine.length(); ++i) {
                        char c = packedLine.charAt(i);
                        // System.out.print(c);
                        if (bracketOpen) {
                            if (c == ']') {
                                bracketOpen = false;
                                // System.out.println("<DISTR>="+Utils.DQuotes(distr));
                                if (distr.equals("block"))
                                    pragmaMax++;
                                distr = "";
                            } else {
                                distr += c;
                            }
                        } else {
                            if (c == '[') {
                                bracketOpen = true;
                            } else {
                                break;
                            }
                        }
                    }
                    // System.out.println("< - " + pragmaMax);
                    fileMax = Math.max(fileMax, pragmaMax);
                }
                ++n;
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return fileMax;
    }
    public static int getCProjectMaxDim(db_project_info project) {
        int res = 0;
        for (DBProjectFile file : project.db.files.Data.values()) {
            if (file.isActiveProgram())
                res = Math.max(res, getCTestMaxDim(file.file));
        }
        return res;
    }
    public static Vector<String> unpackStrings(String string, boolean brackets) {
        Vector<String> res = new Vector<>();
        if (string.isEmpty())
            res.add(brackets ? "[]" : "");
        else {
            StringBuilder line = new StringBuilder();
            for (char c : string.toCharArray()) {
                if (c == '\n') {
                    res.add(brackets ? Utils.Brackets(line.toString()) : line.toString());
                    line = new StringBuilder();
                } else
                    line.append(c);
            }
        }
        return res;
    }
    public static Vector<String> unpackStrings(String string) {
        return unpackStrings(string, false);
    }
    public static boolean isTimeout(long startDate, long maxtime_sec) {
        Date now = new Date();
        long delta = (now.getTime() - startDate) / 1000;
        return (delta > maxtime_sec);
    }
    //https://translated.turbopages.org/proxy_u/en-ru.ru.596e2df7-62fd38bb-6b4aad49-74722d776562/https/stackoverflow.com/questions/34658054/finding-whole-word-only-in-java-string-search
    public static long findInFile(String keyword_in, boolean registerOn, boolean wholeWord, File file) {
        if (keyword_in.isEmpty()) return 0;
        long res = 0;
        try {
            String text = FileUtils.readFileToString(file);
            String keyword = keyword_in;
            //-->>
            if (!registerOn) {
                text = text.toUpperCase();
                keyword = keyword.toUpperCase();
            }
            String regex = wholeWord ? "\\b" + keyword + "\\b" : keyword;
            Pattern pattern = Pattern.compile(regex);
            Matcher matcher = pattern.matcher(text);
            while (matcher.find()) res++;
            System.out.println("matches=" + res);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return res;
    }
    public static String strikeThrough(String s) {
        StringBuilder res = new StringBuilder();
        for (char c : s.toCharArray()) {
            res.append(c);
            if (c != Constants.toStrike)
                res.append(Constants.toStrike);
        }
        return res.toString();
    }
    public static String noStrikeThrough(String s) {
        StringBuilder res = new StringBuilder();
        for (char c : s.toCharArray()) {
            if (c != (Constants.toStrike))
                res.append(c);
        }
        return res.toString();
    }
    public static void addEmptyLines(Vector<String> lines, int num) {
        IntStream.range(0, num).mapToObj(i -> "").forEach(lines::add);
    }
    public static String jsonToPrettyFormat(String jsonString) {
        JsonParser parser = new JsonParser();
        JsonObject json = parser.parse(jsonString).getAsJsonObject();
        Gson gson = new GsonBuilder().setPrettyPrinting().create();
        return gson.toJson(json);
    }
    protected static boolean isSource(File file) {
        if (file.isFile()) {
            String extension = getExtension(file).toLowerCase();
            switch (extension) {
                case "f":
                case "fdv":
                case "for":
                case "f77":
                case "f90":
                    //   case "fh":
                case "c":
                case "cdv":
                case "cpp":
                    //   case "h":
                    return true;
            }
        }
        return false;
    }
    public static boolean containsSource(File folder, boolean question) {
        File visualiserData = new File(folder, Constants.data);
        if (visualiserData.exists()) {
            return true;
        }     //есть папка с данными, значит его уже открывали, все хорошо.
        File[] files = folder.listFiles();
        Vector<File> sources = new Vector<>();
        if (files != null) {
            for (File file : files) {
                // System.out.println(file.getAbsolutePath());
                if (isSource(file)) {
                    //    System.out.println(file.getAbsolutePath() + " is source!");
                    sources.add(file);
                }
            }
        }
        if (sources.isEmpty()) {
            if (question) return UI.Question("Папка " + Brackets(folder.getName()) + "\n" +
                    "не содержит ни одного файла, распознанного как поддерживаемый код\n" +
                    "Всё равно открыть её как проект");
            else return false;
        } else return true;
    }
    public static void Kill(String PID, boolean force) {
        if (!PID.isEmpty()) {
            String killCommand =
                    force ? Global.isWindows ? "taskkill /PID " + Utils.DQuotes(PID) + " /F /T" : "kill -9 " + Utils.DQuotes(PID) :
                            Global.isWindows ? "taskkill /PID " + Utils.DQuotes(PID) : "kill -2 " + Utils.DQuotes(PID);
            System.out.println(killCommand);
            try {
                Process killer = Utils.startScript(Global.TempDirectory, Global.TempDirectory, "killer", killCommand);
                killer.waitFor();
                System.out.println(Utils.readAllOutput(killer));
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
    public static boolean isFunctionName(String name) {
        if (name.isEmpty())
            return false;
        char[] letters = name.toCharArray();
        if (!(isEnglishLetter(letters[0]) || letters[0] == '_')) {
            return false;
        }
        //---
        for (int i = 1; i < letters.length; ++i) {
            if (!(isEnglishLetter(letters[i]) || letters[i] == '_' || Utils.isDigit(String.valueOf(letters[i])))) {
                return false;
            }
        }
        return true;
    }
    public static int fromBoolean(boolean flag) {
        return flag ? 1 : 0;
    }
    public static void keepNewFiles(File directory, int count) throws Exception {
        if (count > 0) {
            File[] old_ = directory.listFiles(pathname -> pathname.isFile());
            if (old_ != null) {
                Vector<File> old = new Vector<>();
                Collections.addAll(old, old_);
                old.sort((o1, o2) -> Long.compare(o2.lastModified(), o1.lastModified()));
                for (int i = count - 1; i < old.size(); ++i) {
                    File file = old.get(i);
                    System.out.println(file.getName() + ":" + file.lastModified());
                    FileUtils.forceDelete(file);
                }
            }
        }
    }
    public static boolean isParallelVersionName(String name) {
        char[] chars = name.toLowerCase().toCharArray();
        if ((chars.length > 1) && (chars[0] == 'p')) {
            for (int i = 1; i < chars.length; ++i) {
                if (!Character.isDigit(chars[i])) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }
    //временно.
    public static int getTestingMaxKernels() {
        return 64; // (Current.getAccount().isAdmin()) ? 64 : 14;
    }
    //--
    public static boolean isCrushedLine(String line) {
        String l_line = line.toLowerCase();
        for (String crushed : Constants.crushed_cases)
            if (l_line.contains(crushed))
                return true;
        return false;
    }
    public static boolean isCrushed(List<String> output_lines, List<String> errors_lines) {
        return output_lines.stream().anyMatch(Utils::isCrushedLine) || errors_lines.stream().anyMatch(Utils::isCrushedLine);
    }
    public static Pair<TaskState, Integer> analyzeCorrectness(List<String> lines) {
        int complete = 0;
        int errors = 0;
        int total = 0;
        int starts = 0;
        int ends = 0;
        for (String s : lines) {
            String line = s.toUpperCase();
            if (line.contains("COMPLETE")) {
                complete++;
                total++;
            } else if (line.contains("ERROR")) {
                errors++;
                total++;
            } else if (line.contains("START")) {
                starts++;
            } else if (line.contains("END")) {
                ends++;
            }
        }
        TaskState state = TaskState.Finished;
        if (starts != ends) {
            state = TaskState.WrongTestFormat;
        } else if (errors > 0) {
            state = TaskState.DoneWithErrors;
        } else {
            state = TaskState.Done;
        }
        return new Pair<>(state, (int) ((((double) complete) / total) * 100));
    }
    public static Pair<TaskState, Integer> analyzePerformance(List<String> lines) {
        StringTemplate stringTemplate = new StringTemplate("Verification =", "");
        for (String line : lines) {
            String param = stringTemplate.check_and_get_param(line);
            if (param != null) {
                switch (param) {
                    case "SUCCESSFUL":
                        return new Pair<>(TaskState.Done, 100);
                    case "UNSUCCESSFUL":
                        return new Pair<>(TaskState.DoneWithErrors, 0);
                    default:
                        break;
                }
            }
        }
        return new Pair<>(TaskState.WrongTestFormat, 0);
    }
    public static double parseCleanTime(String output) {
        double res = 0.0;
        StringTemplate template = new StringTemplate("Time in seconds =", "");
        String p = template.check_and_get_param(output);
        try {
            if (p != null) res = Double.parseDouble(p);
        } catch (Exception ex) {
            Global.Log.PrintException(ex);
        }
        return res;
    }
    public static void RestoreSelectedDirectory(VFileChooser_ directoryChooser) {
        String last_dir_home =
                Global.db.settings.get(SettingName.ProjectsSearchDirectory).Value;
        if (!last_dir_home.isEmpty())
            directoryChooser.SetCurrentDirectory(last_dir_home);
    }
    public static void ClearProjectData(File project_home) throws Exception {
        Utils.deleteFilesByExtensions(project_home, "dep", "proj");
        File project_data = new File(project_home, Constants.data);
        if (project_data.exists())
            FileUtils.forceDelete(project_data);
    }
    //--
    public static boolean isVersion(File directory) throws Exception {
        return new File(directory, Constants.data).exists();
    }
    public static String printSplittedDateInterval(long milliseconds) {
        //--
        long seconds = milliseconds / 1000;
        long hours = seconds / 3600;
        seconds = seconds - hours * 3600;
        long minutes = (seconds) / 60;
        seconds = seconds - minutes * 60;
        //--
        return hours + " часов, " + minutes + " минут, " + seconds + " секунд";
    }
}

