package GlobalData.Makefile;
import Common.Current;
import Common.Global;
import Common.Utils.TextLog;
import Common.Utils.Utils;
import GlobalData.Compiler.Compiler;
import GlobalData.Compiler.CompilerType;
import GlobalData.Machine.Machine;
import GlobalData.Module.Module;
import GlobalData.Module.ModuleAnchestor;
import ProjectData.Files.DBProjectFile;
import ProjectData.LanguageName;
import ProjectData.Project.db_project_info;

import java.util.LinkedHashMap;
import java.util.Vector;
import java.util.stream.Collectors;
//мейкфайл. цепочка модулей. он сам отвечает за линковку.
//И по одному модулю на каждый язык.
public class Makefile extends ModuleAnchestor {
    public Makefile() {
    }
    @Override
    public boolean isVisible() {
        return Current.HasMachine() && (machine_id == Current.getMachine().id);
    }
    public LinkedHashMap<LanguageName, Module> getActiveModules() {
        return Global.db.getByFKAndGroupBy(this, Module.class, "language", LanguageName.class).values().stream().filter(Module::isSelected).collect(Collectors.toMap(module -> module.language, module -> module, (a, b) -> b, LinkedHashMap::new));
    }
    public LinkedHashMap<LanguageName, Module> getModules() {
        return Global.db.getByFKAndGroupBy(this, Module.class, "language", LanguageName.class);
    }
    public String Generate(db_project_info project, boolean useFilesOrder, LinkedHashMap<LanguageName, Module> modules) throws Exception {
        Compiler linker = getCompiler();
        if (linker == null) return "";
        LinkedHashMap<LanguageName, Vector<DBProjectFile>> programs = project.getPrograms();
        Vector<String> titles = new Vector<>();
        Vector<String> objects = new Vector<>();
        Vector<String> bodies = new Vector<>();
        String binary = Utils.DQuotes("0"); // Utils.DQuotes(project.name);
        for (Module module : modules.values()) {
            //определить а активен ли модуль.
            //выбран ли он. есть ли у него компилятор.  есть ли для него программы.
            Compiler module_compiler;
            if (module.isSelected() &&
                    ((module_compiler = module.getCompiler()) != null)
                    && (!programs.get(module.language).isEmpty())) {
                //---------------------------------------------------------
                String LANG_ = module.language.toString().toUpperCase() + "_";
                Vector<String> module_objects = new Vector<>();
                String module_body = "";
                int i = 1;
//-------------------------------------------------------------------------
                Vector<DBProjectFile> programsToAssembly = new Vector<>();
                if (module.language.equals(LanguageName.fortran) && useFilesOrder) {
                    //для фортрана берем порядок с парсера.
                    for (String progName : project.files_order)
                        programsToAssembly.add(project.db.files.get(progName));
                } else programsToAssembly = programs.get(module.language);
//--------------------------------------------------------------------------
                for (DBProjectFile program : programsToAssembly) {
                    //--
                    program.last_assembly_name = module.language.toString() + "_" + i + ".o";
                    String object = Utils.DQuotes(program.last_assembly_name);
                    module_objects.add(object);
                    module_body +=
                            object + ":\n" +
                                    "\t" +
                                    String.join(" ",
                                            Utils.MFVar(LANG_ + "COMMAND"),
                                            Utils.MFVar(LANG_ + "FLAGS"),
                                            module_compiler.getStyleOptions(program),
                                            "-c",
                                            program.getQSourceName(),
                                            "-o",
                                            object + "\n\n"
                                    );
                    ++i;
                }
                titles.add(String.join("\n",
                        LANG_ + "COMMAND=" + Utils.DQuotes(module_compiler.call_command) + " " + module.command,
                        LANG_ + "FLAGS=" + module.flags,
                        LANG_ + "OBJECTS=" + String.join(" ", module_objects),
                        ""
                ));
                objects.add(Utils.MFVar(LANG_ + "OBJECTS"));
                bodies.add(module_body);
            }
        }
        return String.join("\n",
                "LINK_COMMAND=" + Utils.DQuotes(linker.call_command) + " " + command,
                "LINK_FLAGS=" + flags + "\n",
                String.join("\n", titles),
                "all: " + binary,
                binary + " : " + String.join(" ", objects),
                "\t" + Utils.MFVar("LINK_COMMAND") + " " + Utils.MFVar("LINK_FLAGS") + " " + String.join(" ", objects) + " -o " + binary,
                String.join(" ", bodies));
    }
    public String Generate(db_project_info project_info) throws Exception {
        return Generate(project_info, true, getActiveModules());
    }
    public String GenerateForPrecompilation(db_project_info project, LinkedHashMap<LanguageName, Module> modules) {
        Compiler linker = getCompiler();
        if (linker == null) return "";
        LinkedHashMap<LanguageName, Vector<DBProjectFile>> programs = project.getPrograms();
        Vector<String> titles = new Vector<>();
        Vector<String> objects = new Vector<>();
        Vector<String> bodies = new Vector<>();
        for (Module module : modules.values()) {
            //определить а активен ли модуль.
            //выбран ли он. есть ли у него компилятор.  есть ли для него программы.
            Compiler module_compiler;
            if (module.isSelected() &&
                    ((module_compiler = module.getCompiler()) != null)
                    && (!programs.get(module.language).isEmpty())) {
                //---------------------------------------------------------
                String LANG_ = module.language.toString().toUpperCase() + "_";
                Vector<String> module_objects = new Vector<>();
                String module_body = "";
                int i = 1;
//-------------------------------------------------------------------------
                Vector<DBProjectFile> programsToAssembly = programs.get(module.language);
//--------------------------------------------------------------------------
                for (DBProjectFile program : programsToAssembly) {
                    //--
                    program.last_assembly_name = module.language.toString() + "_" + i + ".o";
                    String object = Utils.DQuotes(program.last_assembly_name);
                    module_objects.add(object);
                    module_body +=
                            object + ":\n" +
                                    "\t" +
                                    String.join(" ",
                                            Utils.MFVar(LANG_ + "COMMAND"),
                                            Utils.MFVar(LANG_ + "FLAGS"),
                                            module_compiler.getStyleOptions(program),
                                            "-c",
                                            program.getQSourceName() + "\n\n"
                                            //  "-o",
                                            //  object
                                    );
                    ++i;
                }
                titles.add(String.join("\n",
                        LANG_ + "COMMAND=" + Utils.DQuotes(module_compiler.call_command) + " " + module.command,
                        LANG_ + "FLAGS=" + module.flags,
                        LANG_ + "OBJECTS=" + String.join(" ", module_objects),
                        ""
                ));
                objects.add(Utils.MFVar(LANG_ + "OBJECTS"));
                bodies.add(module_body);
            }
        }
        return String.join("\n",
                String.join("\n", titles),
                "all: " + String.join(" ", objects),
                String.join(" ", bodies));
    }
    //---------------------------------------
    //todo возможно улучшить
    //https://habr.com/ru/post/211751/
    public Machine getMachine() {
        return Global.db.getById(Machine.class, machine_id);
    }
    public boolean DependsToCompiler(Compiler compiler) {
        if (compiler_id == compiler.id) return true;
        else {
            LinkedHashMap<LanguageName, Module> modules = getModules();
            for (Module module : modules.values())
                if (module.compiler_id == compiler.id) return true;
        }
        return false;
    }
    public void Validate(TextLog Log) {
        Compiler linker;
        if ((linker = getCompiler()) == null)
            Log.Writeln("Линковщик не выбран");
        else {
            if (linker.type.equals(CompilerType.dvm)) {
                if (!Current.getProject().languageName.getDVMLink().equals(command))
                    Log.Writeln("команда линковки " +
                            Utils.Quotes(command) +
                            " не соответствует языку текущего проекта "
                            + Current.getProject().languageName.getDescription() + "\n" +
                            "Используйте команду " + Current.getProject().languageName.getDVMLink());
            }
        }
        LinkedHashMap<LanguageName, Module> modules = getModules();
        for (Module module : modules.values()) {
            boolean isMain = module.language.equals(Current.getProject().languageName);
            if (module.isSelected()) {
                if (module.getCompiler() == null)
                    Log.Writeln("Не назначен компилятор для языка " + module.language.getDescription());
                if (isMain && Current.getProject().getPrograms().get(module.language).isEmpty())
                    Log.Writeln("В текущем проекте не найдено ни одной программы на языке " + module.language.getDescription());
            } else {
                if (isMain)
                    Log.Writeln("Языковой модуль, соответствующий языку текущего проекта " +
                            Current.getProject().languageName.getDescription() + " не помечен как активный.");
            }
        }
    }
}
