package _VisualDVM.TestingSystem.DVM;
import Common.CommonConstants;
import Common.Passes.PassException;
import Common.Utils.Pair;
import Common.Utils.Utils_;
import _VisualDVM.Constants;
import _VisualDVM.GlobalData.Machine.Machine;
import _VisualDVM.GlobalData.RemoteFile.RemoteFile;
import _VisualDVM.GlobalData.User.User;
import _VisualDVM.ProjectData.Project.db_project_info;
import _VisualDVM.Utils;
import com.jcraft.jsch.*;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Vector;
public class UserConnection {
    //http://www.jcraft.com/jsch/
    public int iterations = 0; //для тестирования
    //--
    public ChannelSftp sftpChannel = null;
    public ChannelShell shellChannel = null;
    public ChannelExec execChannel = null;
    //--
    JSch jsch = null;
    Session session = null;
    //---
    PipedInputStream in = null;
    PipedOutputStream out = null;
    //---
    PipedOutputStream pin = null;
    PipedInputStream pout = null;
    InputStreamReader fromServer = null;
    //---
    Machine machine = null;
    User user = null;
    //---
    public UserConnection(Machine machine_in, User user_in) throws Exception {
        machine = machine_in;
        user = user_in;
        //--
        session = (jsch = new JSch()).getSession(user.login, machine.address, machine.port);
        session.setPassword(user.password);
        session.setConfig("StrictHostKeyChecking", "no");
        session.connect(0);
        //-->
        //создать канал для файлов
        sftpChannel = (ChannelSftp) session.openChannel("sftp");
        sftpChannel.connect();
        //-->
        //создать канал для команд
    }
    public void Disconnect() {
        if (in != null) {
            try {
                in.close();
            } catch (Exception exception) {
                Utils_.MainLog.PrintException(exception);
            }
        }
        if (out != null) {
            try {
                out.close();
            } catch (Exception exception) {
                Utils_.MainLog.PrintException(exception);
            }
        }
        if (pin != null) {
            try {
                pin.close();
            } catch (Exception exception) {
                Utils_.MainLog.PrintException(exception);
            }
        }
        if (pout != null) {
            try {
                pout.close();
            } catch (Exception exception) {
                Utils_.MainLog.PrintException(exception);
            }
        }
        if (fromServer != null) {
            try {
                fromServer.close();
            } catch (Exception exception) {
                Utils_.MainLog.PrintException(exception);
            }
        }
        if (sftpChannel != null) sftpChannel.disconnect();
        if (shellChannel != null) shellChannel.disconnect();
        if (execChannel != null) execChannel.disconnect();
        if (session != null) {
            session.disconnect();
        }
        //----------------------
        sftpChannel = null;
        shellChannel = null;
        execChannel = null;
        session = null;
        //---
        in = null;
        out = null;
        //---
        pin = null;
        pout = null;
        fromServer = null;
        jsch = null;
        System.gc();
    }
    public void getSingleFile(String src, String dst) throws Exception {
        sftpChannel.get(src, dst);
    }
    public long getFileKBSize(String path) throws Exception {
        long size = sftpChannel.lstat(path).getSize();
        return size / 1024;
    }
    public void getSingleFileWithMaxSize(RemoteFile src, File dst, int maxSize) throws Exception {
        if ((maxSize == 0) || getFileKBSize(src.full_name) <= maxSize) {
            getSingleFile(src.full_name, dst.getAbsolutePath());
        } else {
            Utils.WriteToFile(dst, "Размер файла превышает " + maxSize + " KB.\n" + "Файл не загружен. Его можно просмотреть на машине по адресу\n" + Utils_.Brackets(src.full_name));
        }
    }
    public void putSingleFile(File src, RemoteFile dst) throws Exception {
        sftpChannel.put(src.getAbsolutePath(), dst.full_name);
    }
    //-
    public void MKDIR(RemoteFile dir) throws Exception {
        if (!Exists(dir)) sftpChannel.mkdir(dir.full_name);
    }
    //-
    public void RMDIR(String dir) throws Exception {
        if (!dir.isEmpty() && !dir.equals("/") && !dir.equals("\\") && !dir.equals("*")) {
            Command("rm -rf " + Utils_.DQuotes(dir));
        } else throw new PassException("Недопустимый путь для удаления папки " + Utils_.DQuotes(dir));
    }
    //-
    public void SynchronizeSubDirsR(File local_dir, RemoteFile remote_dir) throws Exception {
        File[] local_subdirs = local_dir.listFiles(File::isDirectory);
        File[] local_files = local_dir.listFiles(File::isFile);
        //------------------------------------------------------------------------
        LinkedHashMap<String, RemoteFile> remote_subdirs = new LinkedHashMap<>();
        LinkedHashMap<String, RemoteFile> remote_files = new LinkedHashMap<>();
        Vector<ChannelSftp.LsEntry> files = sftpChannel.ls(remote_dir.full_name);
        for (ChannelSftp.LsEntry file : files) {
            if (file.getAttrs().isDir()) {
                if (!file.getFilename().equals(".") && !file.getFilename().equals(".."))
                    remote_subdirs.put(file.getFilename(), new RemoteFile(remote_dir.full_name, file.getFilename(), true));
            } else {
                RemoteFile rf = new RemoteFile(remote_dir.full_name, file.getFilename());
                rf.updateTime = RemoteFile.convertUpdateTime(file.getAttrs().getMTime());
                remote_files.put(file.getFilename(), rf);
            }
        }
        if (local_subdirs != null) {
            for (File lsd : local_subdirs) {
                if (!lsd.getName().equals(Constants.data)) {
                    RemoteFile rsd = null;
                    if (!remote_subdirs.containsKey(lsd.getName()))
                        sftpChannel.mkdir((rsd = new RemoteFile(remote_dir.full_name, lsd.getName(), true)).full_name);
                    else rsd = remote_subdirs.get(lsd.getName());
                    SynchronizeSubDirsR(lsd, rsd);
                }
            }
        }
        if (local_files != null) {
            for (File lf : local_files) {
                RemoteFile rf = null;
                if (!remote_files.containsKey(lf.getName())) {
                    rf = new RemoteFile(remote_dir.full_name, lf.getName());
                    putSingleFile(lf, rf);
                } else {
                    rf = remote_files.get(lf.getName());
                    if (lf.lastModified() > rf.updateTime) {
                        putSingleFile(lf, rf);
                    }
                }
            }
        }
    }
    public void writeToFile(String text, RemoteFile dst) throws Exception {
        sftpChannel.put(new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8)), dst.full_name);
        sftpChannel.chmod(0777, dst.full_name);
    }
    public String readFromFile(RemoteFile src) throws Exception {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        sftpChannel.get(src.full_name, outputStream);
        return outputStream.toString(StandardCharsets.UTF_8.name());
    }
    //--
    public boolean Exists(String file_full_name) throws Exception {
        try {
            sftpChannel.lstat(file_full_name);
            return true;
        } catch (SftpException e) {
            if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
                // file doesn't exist
                return false;
            } else {
                // something else went wrong
                throw e;
            }
        }
    }
    public boolean Busy(String file_full_name) throws Exception {
        try {
            sftpChannel.lstat(file_full_name);
            return true;
        } catch (SftpException e) {
            if (e.id == ChannelSftp.SSH_FX_PERMISSION_DENIED) {
                // file busy
                return false;
            } else {
                // something else went wrong
                throw e;
            }
        }
    }
    public boolean Busy(RemoteFile file) throws Exception {
        return Busy(file.full_name);
    }
    public boolean Exists(RemoteFile file) throws Exception {
        return Exists(file.full_name);
    }
    //--
    public Pair<RemoteFile, RemoteFile> performScript(RemoteFile directory, String... commands) throws Exception {
        RemoteFile script_file = new RemoteFile(directory, Constants.script);
        RemoteFile out = new RemoteFile(directory, Constants.out_file);
        RemoteFile err = new RemoteFile(directory, Constants.err_file);
        //
        Vector<RemoteFile> files = new Vector<>();
        files.add(script_file);
        files.add(out);
        files.add(err);
        for (RemoteFile file : files) {
            if (Exists(file))
                sftpChannel.rm(file.full_name);
        }
        //--
        writeToFile("cd " + Utils_.DQuotes(directory.full_name) + "\n" + String.join("\n", commands), script_file);
        //--
        Command(Utils_.DQuotes(script_file.full_name) + " 1>" + Utils_.DQuotes(out.full_name) + " 2>" + Utils_.DQuotes(err.full_name));
        return new Pair<>(out, err);
    }
    public void putResource(RemoteFile dstDirectory, String resource_name) throws Exception {
        File src = Utils.CreateTempResourceFile(resource_name);
        RemoteFile dst = new RemoteFile(dstDirectory, resource_name);
        putSingleFile(src, dst);
    }
    boolean compileModule(RemoteFile modulesDirectory, String module_name) throws Exception {
        boolean planner = module_name.equals("planner");
        String flags = planner ? getAvailibleCPPStandard(modulesDirectory) : "";
        String command = "g++ -O3 " + flags + " " + Utils_.DQuotes(module_name + ".cpp") + " -o " + Utils_.DQuotes(module_name);
        if (planner && flags.contains("c++17")) command += " -lstdc++fs";
        RemoteFile binary = new RemoteFile(modulesDirectory, module_name);
        //--
        if (Exists(binary))
            sftpChannel.rm(binary.full_name);
        //--
        performScript(modulesDirectory, command);
        //--
        if (Exists(binary)) {
            sftpChannel.chmod(0777, binary.full_name);
            return true;
        }
        return false;
    }
    String getAvailibleCPPStandard(RemoteFile scriptDirectory) throws Exception {
        String res = "";
        String command = "g++ -v --help 2> /dev/null | sed -n '/^ *-std=\\([^<][^ ]\\+\\).*/ {s//\\1/p}' | grep c++";
        Pair<RemoteFile, RemoteFile> oe = performScript(scriptDirectory, command);
        RemoteFile outFile = oe.getKey();
        String out = readFromFile(outFile);
        //--
        RemoteFile cppVersionsInfo = new RemoteFile(scriptDirectory, "cpp_versions.txt");
        sftpChannel.rename(outFile.full_name, cppVersionsInfo.full_name);
        //--
        String[] data = out.split("\n");
        boolean cpp_17 = false;
        boolean cpp_11 = false;
        //определить какие есть версии.
        for (String version : data) {
            switch (version) {
                case "c++17":
                    cpp_17 = true;
                    break;
                case "c++11":
                    cpp_11 = true;
                    break;
            }
        }
        if (cpp_17)
            res = "-std=c++17";
        else if (cpp_11)
            res = "-std=c++11";
        RemoteFile cppFlag = new RemoteFile(scriptDirectory, "current_ccp_version");
        writeToFile(res, cppFlag);
        return res;
    }
    public String compileModules(RemoteFile modulesDirectory) throws Exception {
        if (!compileModule(modulesDirectory, "launcher")) {
            return "Не удалось собрать модуль [launcher]";
        }
        if (!compileModule(modulesDirectory, "starter")) {
            return "Не удалось собрать модуль [starter]";
        }
        if (!compileModule(modulesDirectory, "planner")) {
            return "Не удалось собрать модуль [planner]";
        }
        return "";
    }
    //--
    public void tryRM(RemoteFile file) throws Exception {
        if (Exists(file))
            sftpChannel.rm(file.full_name);
    }
    //с проверкой.
    public boolean tryGetSingleFileWithMaxSize(RemoteFile src, File dst, int maxSize) throws Exception {
        if (Exists(src)) {
            if ((maxSize == 0) || (getFileKBSize(src.full_name) <= maxSize)) {
                getSingleFile(src.full_name, dst.getAbsolutePath());
                return true;
            } else {
                Utils.WriteToFile(dst, "Размер файла превышает " + maxSize + " KB.\n" + "Файл не загружен. Его можно просмотреть на машине по адресу\n" + Utils_.Brackets(src.full_name));
            }
        }
        return false;
    }
    //--
    public void SynchronizeProjectSubDirsR(db_project_info project, File local_dir, RemoteFile remote_dir, boolean data) throws Exception {
        // ShowMessage2("синхронизация: " + local_dir.getName());
        Vector<File> local_subdirs = project.getSubdirectoriesSimple(local_dir);
        Vector<File> local_files = project.getActiveFilesForSynchronization(local_dir, data);
        //------------------------------------------------------------------------
        LinkedHashMap<String, RemoteFile> remote_subdirs = new LinkedHashMap<>();
        LinkedHashMap<String, RemoteFile> remote_files = new LinkedHashMap<>();
        Vector<ChannelSftp.LsEntry> files = sftpChannel.ls(remote_dir.full_name);
        for (ChannelSftp.LsEntry file : files) {
            if (file.getAttrs().isDir()) {
                if (!file.getFilename().equals(".") && !file.getFilename().equals(".."))
                    remote_subdirs.put(file.getFilename(), new RemoteFile(remote_dir.full_name, file.getFilename(), true));
            } else {
                RemoteFile rf = new RemoteFile(remote_dir.full_name, file.getFilename());
                rf.updateTime = RemoteFile.convertUpdateTime(file.getAttrs().getMTime());
                remote_files.put(file.getFilename(), rf);
            }
        }
        for (File lsd : local_subdirs) {
            RemoteFile rsd = null;
            if (!remote_subdirs.containsKey(lsd.getName()))
                sftpChannel.mkdir((rsd = new RemoteFile(remote_dir.full_name, lsd.getName(), true)).full_name);
            else rsd = remote_subdirs.get(lsd.getName());
            SynchronizeProjectSubDirsR(project, lsd, rsd, data);
        }
        for (File lf : local_files) {
            RemoteFile rf = null;
            if (!remote_files.containsKey(lf.getName())) {
                rf = new RemoteFile(remote_dir.full_name, lf.getName());
                //  ShowMessage2(lf.getName());
                putSingleFile(lf, rf);
            } else {
                rf = remote_files.get(lf.getName());
                if (lf.lastModified() > rf.updateTime) {
                    //  ShowMessage2(lf.getName());
                    putSingleFile(lf, rf);
                }
            }
        }
    }
    //--
    public Vector<RemoteFile> getFilesByExtensions(RemoteFile dir, String... extensions) throws Exception {
        Vector<RemoteFile> res = new Vector<>();
        Vector<ChannelSftp.LsEntry> files = sftpChannel.ls(dir.full_name);
        for (ChannelSftp.LsEntry file : files) {
            String[] data = file.getFilename().split("\\.");
            if (data.length > 1) {
                String file_extension = data[data.length - 1];
                for (String extension : extensions) {
                    if (file_extension.equalsIgnoreCase(extension))
                        res.add(new RemoteFile(dir.full_name, file.getFilename()));
                }
            }
        }
        return res;
    }
    public void deleteFilesByExtensions(RemoteFile dir, String... extensions) throws Exception {
        Vector<RemoteFile> to_delete = getFilesByExtensions(dir, extensions);
        for (RemoteFile file : to_delete)
            sftpChannel.rm(file.full_name);
    }
    public void Command(String... commands) throws Exception {
        if (commands.length > 0) {
            String command = String.join("\n", commands);
            execChannel = (ChannelExec) session.openChannel("exec");
            execChannel.setErrStream(System.err);
            execChannel.setCommand(command);
            execChannel.connect();
            BufferedReader in = new BufferedReader(new InputStreamReader(execChannel.getInputStream()));
            String line = null;
            while ((line = in.readLine()) != null) {
                System.out.println(Utils_.Brackets(line));
            }
        }
        execChannel.disconnect();
    }
    public void CommandNoWait(String... commands) throws Exception {
        if (commands.length > 0) {
            String command = String.join("\n", commands);
            execChannel = (ChannelExec) session.openChannel("exec");
            execChannel.setErrStream(System.err);
            execChannel.setCommand(command);
            execChannel.connect();
            execChannel.disconnect();
        }
    }
    //-----
/*
    public void ShellConnect() throws Exception {
        shellChannel = (ChannelShell) session.openChannel("shell");
        in = new PipedInputStream();
        out = new PipedOutputStream();
        //--
        shellChannel.setInputStream(in);
        shellChannel.setOutputStream(out);
        pin = new PipedOutputStream(in);
        pout = new PipedInputStream(out);
        shellChannel.connect();
        //-
        fromServer = new InputStreamReader(pout);
    }
    public void ShellDisconnect() throws Exception {
        if (in != null) {
            try {
                in.close();
            } catch (Exception exception) {
                Utils_.MainLog.PrintException(exception);
            }
        }
        if (out != null) {
            try {
                out.close();
            } catch (Exception exception) {
                Utils_.MainLog.PrintException(exception);
            }
        }
        if (pin != null) {
            try {
                pin.close();
            } catch (Exception exception) {
                Utils_.MainLog.PrintException(exception);
            }
        }
        if (pout != null) {
            try {
                pout.close();
            } catch (Exception exception) {
                Utils_.MainLog.PrintException(exception);
            }
        }
        if (fromServer != null) {
            try {
                fromServer.close();
            } catch (Exception exception) {
                Utils_.MainLog.PrintException(exception);
            }
        }
        if (shellChannel != null) shellChannel.disconnect();
        //--
        in = null;
        out = null;
        pin = null;
        pout = null;
        fromServer = null;
        shellChannel = null;
        System.gc();
    }
 */
    public void waitForFileCreation(RemoteFile file) throws Exception {
        while (!Exists(file)) {
            System.out.println(file.full_name + " NOT FOUND");
            Utils_.sleep(1000);
        }
    }
    public void waitForFileFree(RemoteFile file) throws Exception {
        while (!Busy(file)) {
            System.out.println(file.full_name + " PERMISSION DENIED");
            Utils_.sleep(1000);
        }
    }
    public void startShellProcess(RemoteFile directory, String outFileName, String... commands) throws Exception {
        Vector<String> commands_ = new Vector<>();
        commands_.add("cd " + Utils_.DQuotes(directory.full_name));
        for (int i = 0; i < commands.length; ++i) {
            if (i == commands.length - 1) {
                commands_.add(commands[i] + " 1>" + Utils_.DQuotes(outFileName) + " 2>err.txt");
            } else {
                commands_.add(commands[i]);
            }
        }
        RemoteFile script_file = new RemoteFile(directory, Constants.script);
        if (Exists(script_file))
            sftpChannel.rm(script_file.full_name);
        writeToFile(String.join("\n", commands_), script_file);
        String start_command = Utils_.DQuotes(script_file.full_name);
        //--
        RemoteFile outFile = new RemoteFile(directory, outFileName);
        if (Exists(outFile))
            sftpChannel.rm(outFile.full_name);
        //--
        System.out.println("nohup " + start_command + " &\r\n");
        execChannel = (ChannelExec) session.openChannel("exec");
        execChannel.setErrStream(System.err);
        execChannel.setCommand("nohup " + start_command + " &\r\n");
        execChannel.connect();
        execChannel.disconnect();
        System.out.println("done");
    }
    //-- проверка существования рабочего пространства.
    public void CheckUserInitialization(String email) throws Exception {
        RemoteFile userWorkspace = new RemoteFile(user.workspace, true);
        if (!Exists(userWorkspace)) {
            sftpChannel.mkdir(userWorkspace.full_name);
            //--
            RemoteFile modulesDirectory = new RemoteFile(userWorkspace, "modules");
            RemoteFile projectsDirectory = new RemoteFile(userWorkspace, "projects");
            RemoteFile testsDirectory = new RemoteFile(userWorkspace, "tests");
            //--
            Vector<RemoteFile> subdirectories = new Vector<>();
            subdirectories.add(modulesDirectory);
            subdirectories.add(projectsDirectory);
            subdirectories.add(testsDirectory);
            for (RemoteFile remoteFile : subdirectories)
                sftpChannel.mkdir(remoteFile.full_name);
            //--
            for (String resource_name : Constants.resourses_names) {
                putResource(modulesDirectory, resource_name);
            }
            //-
            String modules_log = compileModules(modulesDirectory);
            if (!modules_log.isEmpty())
                throw new PassException(modules_log);
            //--
            RemoteFile info = new RemoteFile(userWorkspace, email);
            user.connection.writeToFile("", info);
        }
    }
    //--
    public String CheckModulesVersion() throws Exception {
        RemoteFile modulesDirectory = new RemoteFile(user.workspace, "modules");
        RemoteFile version = new RemoteFile(modulesDirectory, "version.h");
        int current_version = CommonConstants.Nan;
        int actual_version = Constants.planner_version;
        if (Exists(version)) {
            try {
                current_version = Integer.parseInt(readFromFile(version));
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        if (current_version < actual_version) {
            for (String resource_name : Constants.resourses_names) {
                putResource(modulesDirectory, resource_name);
            }
            //--
            return compileModules(modulesDirectory);
        }
        return "";
    }
}