package _VisualDVM.Passes.All;
import Common.CommonConstants;
import Common.Passes.Pass;
import Common.Passes.PassException;
import Common.Utils.Utils_;
import Common.Visual.UI;
import _VisualDVM.Constants;
import _VisualDVM.Global;
import _VisualDVM.GlobalData.Compiler.Compiler;
import _VisualDVM.GlobalData.Compiler.CompilerType;
import _VisualDVM.GlobalData.Makefile.Makefile;
import _VisualDVM.GlobalData.Module.Module;
import _VisualDVM.Passes.PassCode;
import _VisualDVM.ProjectData.Files.DBProjectFile;
import _VisualDVM.ProjectData.LanguageName;
import _VisualDVM.ProjectData.Project.db_project_info;
import _VisualDVM.Utils;
import org.apache.commons.io.FileUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Paths;
import java.util.LinkedHashMap;
import java.util.Vector;
public class Precompilation extends Pass<db_project_info> {
    protected static String name_to_kill = "";
    protected static boolean killed = false;
    protected String makepath;
    //----------------------->>>
    protected File workspace;  //рабочая папка.
    protected Compiler fortranCompiler;
    protected Makefile makefile;
    protected File makefileFile;
    protected int performanceTime;
    protected int exitCode;
    //----------------------->>>
    protected Process process = null;
    protected String output = "";
    protected Vector<String> outputLines = new Vector<>();
    protected static void unpackPrecompilationMessages(db_project_info target, String text) throws Exception {
        boolean messageStarted = false;
        String messageFile = "";
        int messageLine = CommonConstants.Nan;
        int messageType = CommonConstants.Nan;
        Vector<String> messageText = new Vector<>();
        String[] nw = text.split("\n");
        for (String S : nw) {
            if (messageStarted) {
                String s = S.toLowerCase();
                if (s.startsWith("warning:")) {
                    messageText.add(S.substring(9));
                    messageType = 0;
                } else if (s.startsWith("error:")) {
                    messageText.add(S.substring(7));
                    messageType = 1;
                } else if (s.startsWith("note:")) {
                    messageText.add(S.substring(6));
                    messageType = 2;
                }
                if (messageType != CommonConstants.Nan) {
                    target.db.files.Data.get(messageFile).CreateAndAddNewMessage(messageType, String.join("\n", messageText), messageLine, Constants.compiler_group);
                    messageStarted = false;
                } else {
                    if (!S.isEmpty())
                        messageText.add(S);
                }
            } else {
                //анализ начала строки.
                for (String name : target.db.files.Data.keySet()) {
                    if (S.startsWith(Utils_.toU(name))) {
                        String[] data = S.split(":");
                        if (data.length > 1) {
                            messageFile = name;
                            messageLine = Integer.parseInt(data[1]);
                            messageType = CommonConstants.Nan;
                            messageText = new Vector<>();
                            messageStarted = true;
                        }
                        break;
                    }
                }
            }
        }
    }
    @Override
    protected boolean needsAnimation() {
        return true;
    }
    @Override
    protected boolean canStart(Object... args) throws Exception {
        target = Global.mainModule.getProject();
        if (Utils_.isWindows()) {
            //--
            makepath = Global.normalProperties.LocalMakePathWindows;
            if (makepath.isEmpty()) {
                Log.Writeln_("Не задан путь к make.exe.\n " +
                        "Укажите его в меню глобальных настроек." +
                        "\nПредварительная компиляция будет отключена.");
                Global.mainModule.getPass(PassCode.UpdateSapforProperty).Do("Precompilation", false);
                return false;
            }
            if (!(new File(makepath).exists())) {
                Log.Writeln_("Файла по пути к make.exe не существует.\n" +
                        "Проверьте правильность его указания в глобальных настройках." +
                        "\nПредварительная компиляция будет отключена.");
                Global.mainModule.getPass(PassCode.UpdateSapforProperty).Do("Precompilation", false);
                return false;
            }
            //--
        }
        process = null;
        if (!target.languageName.equals(LanguageName.fortran)) {
            Log.Writeln_("Предварительная компиляция поддерживается только для FORTRAN.");
            return false;
        }
        return true;
    }
    protected String getLinkFlags() {
        return "";
    }
    protected String getFortranFlags() {
        return Global.normalProperties.PrecompilationFlags;
    }
    protected void prepareForParse() throws Exception {
        target.CleanAnalyses();
    }
    @Override
    protected void performPreparation() throws Exception {
        target.CleanInterruptFile();
        //-
        Global.mainModule.getPass(PassCode.Save).Do(); //---->
        prepareForParse();           //---->
        //-
        performanceTime = 0;
        exitCode = CommonConstants.Nan;
        //------------------
        workspace = Paths.get(Global.TempDirectory.getAbsolutePath(), Utils_.getDateName("precompilation")).toFile();
        FileUtils.forceMkdir(workspace);
        //------------------------------>>
        fortranCompiler = new Compiler();
        fortranCompiler.call_command = "gfortran";
        fortranCompiler.type = CompilerType.gnu;
        makefileFile = Paths.get(workspace.getAbsolutePath(), "Makefile").toFile();
        makefile = new Makefile() {
            @Override
            public Compiler getCompiler() {
                return fortranCompiler;
            }
        };
        makefile.flags = getLinkFlags();
        LinkedHashMap<LanguageName, Module> modules = new LinkedHashMap<>();
        Module fortranModule = new Module(LanguageName.fortran, null) {
            @Override
            public boolean isSelected() {
                return true; //всегда выбран. нужно для генерации.
            }
            @Override
            public Compiler getCompiler() {
                return fortranCompiler;
            }
        };
        fortranModule.flags = getFortranFlags();
        modules.put(LanguageName.fortran, fortranModule);
        //------------------------------>>
        target.Clone(workspace, false);
        //------------------------------>>
        FileUtils.write(makefileFile, genMakefileText(modules));
        //---
    }
    protected String genMakefileText(LinkedHashMap<LanguageName, Module> modules) throws Exception {
        return makefile.GenerateForPrecompilation(target, modules);
    }
    @Override
    protected void showPreparation() throws Exception {
        Global.mainModule.getUI().getMainWindow().getProjectWindow().ShowNoAnalyses();
        if (Global.mainModule.HasFile()) {
            Global.mainModule.getFile().form.ShowNoMessages();
            Global.mainModule.getFile().form.ShowNoAnalyses();
        }
    }
    @Override
    protected void body() throws Exception {
        if (Utils_.isWindows()) {
            name_to_kill = "make.exe";
            StartProcess(Utils_.DQuotes(Global.normalProperties.LocalMakePathWindows) + " -j " + Global.normalProperties.Kernels,
                    target.compilation_maxtime);
        } else
            StartProcess("make -j " + Global.normalProperties.Kernels, target.compilation_maxtime);
    }
    protected void StartProcess(String command, int TA) throws Exception {
        killed = false;
        output = "";
        process = Utils.startScript(workspace, workspace, Utils_.getDateName("start_task_script"),
                command,
                null);
        outputLines.clear();
        InputStream stdout = process.getInputStream();
        InputStreamReader isrStdout = new InputStreamReader(stdout);
        BufferedReader brStdout = new BufferedReader(isrStdout);
        String line;
        //-
        while ((line = brStdout.readLine()) != null) {
            ShowMessage2(line);
            outputLines.add(line);
        }
        output = String.join("\n", outputLines);
        if (killed) {
            throw new PassException("Процесс был убит");
        }
    }
    @Override
    protected boolean validate() {
        return outputLines.stream().noneMatch(
                line -> (line.toLowerCase().startsWith("error")
                        || line.toLowerCase().startsWith("gfortran.exe: error:")
                        || line.toLowerCase().startsWith("gfortran: error:")
                ));
    }
    @Override
    protected void performDone() throws Exception {
    }
    @Override
    protected void performFinish() throws Exception {
        for (DBProjectFile file : target.db.files.Data.values()) {
            if (!file.last_assembly_name.isEmpty()) {
                String replacement = file.last_assembly_name + " " + Utils_.RBrackets(file.name);
                output = output.replace(file.last_assembly_name, replacement);
            }
        }
        target.updateCompilationOut(output);
        //----------------------------------
        target.db.BeginTransaction();
        unpackPrecompilationMessages(target, output);
        for (DBProjectFile file : target.db.files.Data.values())
            file.father.db.Update(file);
        target.db.Commit();
        //----------------------------------
    }
    @Override
    protected void showFinish() throws Exception {
        Global.mainModule.getUI().getMainWindow().getProjectWindow().RefreshProjectTreeAndMessages();
        if (Global.mainModule.HasFile()) {
            Global.mainModule.getFile().form.ShowCompilationOutput();
            Global.mainModule.getFile().form.FocusCompilationOut();
        }
    }
    @Override
    public void Interrupt() throws Exception {
        if (Utils_.isWindows()) {
            if (!name_to_kill.isEmpty()) {
                killed = true;
                Process killer = Runtime.getRuntime().exec("taskkill /FI \"IMAGENAME eq " + name_to_kill + "\" /F /T");
                killer.waitFor();
            }
        } else {
            UI.Info("Прерывание процессов под Linux не реализовано");
        }
    }
}
