package _VisualDVM.ProjectData.Files;
import Common.Utils.Pair;
import Common.Utils.Utils_;
import _VisualDVM.Constants;
import _VisualDVM.Global;
import _VisualDVM.ProjectData.GCOV.GCOV_info;
import _VisualDVM.ProjectData.LanguageName;
import _VisualDVM.ProjectData.Messages.Errors.MessageError;
import _VisualDVM.ProjectData.Messages.Message;
import _VisualDVM.ProjectData.Messages.Notes.MessageNote;
import _VisualDVM.ProjectData.Messages.Warnings.MessageWarning;
import _VisualDVM.ProjectData.Project.db_project_info;
import _VisualDVM.ProjectData.SapforData.Arrays.ArrayDecl;
import _VisualDVM.ProjectData.SapforData.FileObjectWithMessages;
import _VisualDVM.ProjectData.SapforData.Functions.FuncCall;
import _VisualDVM.ProjectData.SapforData.Functions.FuncInfo;
import _VisualDVM.ProjectData.SapforData.Functions.FunctionType;
import _VisualDVM.ProjectData.SapforData.Loops.Loop;
import _VisualDVM.Utils;
import _VisualDVM.Visual.Windows.FileForm;
import com.sun.org.glassfish.gmbal.Description;

import javax.swing.tree.DefaultMutableTreeNode;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Paths;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Vector;
//по файлам нет смысла делать совместимость.
//так что переименую
public class DBProjectFile extends ProjectFile {
    @Description("PRIMARY KEY, UNIQUE")
    public String name;  //имя относительно корневой папки проекта. нужно только как ключ для бд!!
    @Description("IGNORE")
    public String last_assembly_name = ""; //имя объектника.
    //--------------------------------------
    public String options = ""; //пользовательские опции для парсера.
    //---
    @Description("DEFAULT 0")
    public int lastLine = 0;
    @Description("DEFAULT ''")
    public String GCOVLog = "";
    public int isMain = 0; //содержит ли ГПЕ
    @Description("IGNORE")
    public int lines_count = 0;
    @Description("IGNORE")
    public boolean NeedsSave = false;
    @Description("IGNORE")
    public String LoopGraphTitle = Constants.no_data;
    @Description("IGNORE")
    public String CallGraphTitle = Constants.no_data;
    @Description("IGNORE")
    public String ArrayGraphTitle = Constants.no_data;
    public db_project_info father = null;
    public DefaultMutableTreeNode node = null; //узел файла в дереве
    public FileForm form = null; //отображение.
    //сообщения.
    //----------------
    //анализы
    public Vector<Loop> LoopNests = new Vector<>();
    public LinkedHashMap<Integer, Loop> AllLoops = new LinkedHashMap<>();
    public LinkedHashMap<String, FuncInfo> function_decls = new LinkedHashMap<>(); //объявления
    public Vector<ArrayDecl> array_decls = new Vector<>();
    public Vector<String> dependencies = new Vector<>();
    public GCOV_info gcov_info = new GCOV_info();
    private PrintStream Sys;
    public DBProjectFile() {
    }
    public DBProjectFile(File file_, db_project_info father_) {
        Init(file_, father_);
        //имя относительно папки проекта.
        RefreshName();
        AutoDetectProperties(file_.getName());
    }
    public static Pair<Integer, String> decodeParserMessage(String S, String file_in) {
        Integer line = 1;
        String file = file_in;
        String[] data = S.split("\\son\\sline\\s");
        if (data.length > 1) {
            String[] data1 = data[1].split(" ");
            if (data1.length > 0)
                line = Integer.parseInt(data1[0]);
        }
        data = S.split("\\sof\\s");
        if (data.length > 1) {
            String[] data1 = data[1].split(":");
            if (data1.length > 0) {
                file = Utils_.toW(data1[0]);//.substring(1));
//первый символ тут всегда пробел. слеши всегда виндовые.
            }
        }
        return new Pair<>(line, file);
    }
    public void UpdateLastLine(int line_in) {
        if (lastLine != line_in) {
            try {
                lastLine = line_in;
                father.db.Update(this);
            } catch (Exception ex) {
                Utils_.MainLog.PrintException(ex);
            }
        }
    }
    //наследуем от объекта, чтобы использовать интерфейс выбора.
    @Override
    public Object getPK() {
        return name;
    }
    @Description("IGNORE")
    public boolean IsMain() {
        return isMain != 0;
    }
    /*
     * получить имя относительно домашней папки проекта, в рамках текущей ОС    *
     */
    public String getLocalName() {
        return file.getAbsolutePath().substring(father.Home.getAbsolutePath().length());
    }
    public void Init(File file_, db_project_info father_) {
        father = father_;
        file = file_;
    }
    public void RefreshName() {
        String path = file.getAbsolutePath();
        //для совместимости пусть палки будут от винды всегда.
        name = path.substring(father.Home.getAbsolutePath().length() + 1).replace('/', '\\');
    }
    public boolean isMakefile() {
        return file.getName().equalsIgnoreCase("makefile");
    }
    public boolean isActiveProgram() {
        return fileType.equals(FileType.program)
                && languageName.equals(father.languageName) && !state.equals(FileState.Excluded);
    }
    //у хедера язык может быть неизвестен. потому что зависимости еще не искались.
    public boolean isActiveHeader() {
        return fileType.equals(FileType.header) && !state.equals(FileState.Excluded);
    }
    public boolean isActive() {
        return isActiveProgram() || isActiveHeader();
    }
    public File getDepFile() {
        return Paths.get(father.getOptionsDirectory().getAbsolutePath(), getLocalName() + Constants.dep).toFile();
    }
    public File getParserOutFile() {
        return Paths.get(father.getOptionsDirectory().getAbsolutePath(), getLocalName() + Constants.out).toFile();
    }
    public File getParserErrFile() {
        return Paths.get(father.getOptionsDirectory().getAbsolutePath(), getLocalName() + Constants.err).toFile();
    }
    public File getOptionsFile() {
        String path = file.getAbsolutePath().substring(father.Home.getAbsolutePath().length());
        return Paths.get(father.getOptionsDirectory().getAbsolutePath(), path + ".opt").toFile();
    }
    public void CreateParserOptions() throws IOException {
        String default_options = "";
        switch (languageName) {
            case fortran:
                default_options += " -spf -noProject -o " + Utils_.DQuotes(getDepFile().getAbsolutePath());
                switch (style) {
                    case free:
                        default_options += " -f90";
                        break;
                    case fixed:
                        default_options += " -FI";
                        break;
                    case extended:
                        default_options += " -extend_source";
                        break;
                }
                break;
            case c:
                //TODO
                break;
        }
        Utils.WriteToFile(getOptionsFile(), (default_options + " " + options + "\n"));
    }
    public void ReadMessages(String text) throws Exception {
        String[] nw = text.split("\n");
        for (String S : nw) {
            Pair<Integer, String> p = decodeParserMessage(S, name);
            //----
            if (S.toLowerCase().startsWith("note")) {
                father.db.files.Data.get(p.getValue()).CreateAndAddNewMessage(2, S, p.getKey(), Constants.parser_group);
            } else if (S.toLowerCase().startsWith("warning")) {
                father.db.files.Data.get(p.getValue()).CreateAndAddNewMessage(0, S, p.getKey(), Constants.parser_group);
            } else if (S.toLowerCase().startsWith("error"))
                father.db.files.Data.get(p.getValue()).CreateAndAddNewMessage(1, S, p.getKey(), Constants.parser_group);
        }
    }
    public void ReadParseMessages() throws Exception {
        if (getParserOutFile().exists())
            ReadMessages(Utils.ReadAllText(getParserOutFile()));
        if (getParserErrFile().exists())
            ReadMessages(Utils.ReadAllText(getParserErrFile()));
    }
    //важно. тут транзакция своя, оборачивать ее нельзя!
    public void CleanMessages() throws Exception {
        if (!state.equals(FileState.Excluded)) state = FileState.Undefined;
        father.db.BeginTransaction();
        father.db.Update(this);
        Vector<Message> to_delete = new Vector<>();
        for (Message message : father.db.notes.Data.values()) {
            if (message.file.equalsIgnoreCase(name))
                to_delete.add(message);
        }
        for (Message message : father.db.warnings.Data.values()) {
            if (message.file.equalsIgnoreCase(name))
                to_delete.add(message);
        }
        for (Message message : father.db.errors.Data.values()) {
            if (message.file.equalsIgnoreCase(name))
                to_delete.add(message);
        }
        for (Message message : to_delete)
            father.db.Delete(message);
        father.db.Commit();
    }
    public void CleanAll() throws Exception {
        lines_count = 0;
        isMain = 0;
        CleanMessages();
        LoopGraphTitle = Constants.no_data;
        LoopNests.clear();
        ;
        AllLoops.clear();
        CallGraphTitle = Constants.no_data;
        function_decls.clear();
        dependencies.clear();
        ArrayGraphTitle = Constants.no_data;
        array_decls.clear();
        gcov_info.clear();
    }
    public void CreateAndAddNewMessage(int m_type, String m_value, int m_line, int m_group) throws Exception {
        switch (m_type) {
            case 0:
                MessageWarning warning = new MessageWarning(name, m_line, m_value, m_group);
                father.db.Insert(warning);
                father.db.recommendations.addRecommendation(m_group);
                if (state != FileState.HasErrors)
                    state = FileState.HasWarnings;
                break;
            case 1:
                MessageError error = new MessageError(name, m_line, m_value, m_group);
                father.db.Insert(error);
                father.db.recommendations.addRecommendation(m_group);
                state = FileState.HasErrors;
                break;
            case 2:
                MessageNote note = new MessageNote(name, m_line, m_value, m_group);
                father.db.Insert(note);
                father.db.recommendations.addRecommendation(m_group);
                if (state != FileState.HasWarnings && state != FileState.HasErrors)
                    state = FileState.HasNotes;
                break;
        }
    }
    public void AddNewMessage(Message message) throws Exception {
        switch (message.type) {
            case 0:
                MessageWarning warning = new MessageWarning(name, message.line, message.value, message.group);
                father.db.Insert(warning);
                father.db.recommendations.addRecommendation(message.group);
                if (state != FileState.HasErrors)
                    state = FileState.HasWarnings;
                break;
            case 1:
                MessageError error = new MessageError(name, message.line, message.value, message.group);
                father.db.Insert(error);
                father.db.recommendations.addRecommendation(message.group);
                state = FileState.HasErrors;
                break;
            case 2:
                MessageNote note = new MessageNote(name, message.line, message.value, message.group);
                father.db.Insert(note);
                father.db.recommendations.addRecommendation(message.group);
                if (state != FileState.HasWarnings && state != FileState.HasErrors)
                    state = FileState.HasNotes;
                break;
        }
    }
    public DefaultMutableTreeNode show_loop_graph_r(FileObjectWithMessages element) {
        DefaultMutableTreeNode res = new DefaultMutableTreeNode(element);
        if (element instanceof Loop) {
            for (FileObjectWithMessages child : ((Loop) element).getGraphNodes(this))
                res.add(show_loop_graph_r(child));
        }
        return res;
    }
    public DefaultMutableTreeNode getLoopsTree() {
        DefaultMutableTreeNode root = new DefaultMutableTreeNode(LoopGraphTitle);
        for (Loop nest : LoopNests)
            root.add(show_loop_graph_r(nest));
        return root;
    }
    public DefaultMutableTreeNode getFunctionsTree() {
        DefaultMutableTreeNode root = new DefaultMutableTreeNode(CallGraphTitle);
        for (FuncInfo fi : function_decls.values()) {
            DefaultMutableTreeNode node = new DefaultMutableTreeNode(fi);
            for (FuncCall fc : fi.callsFrom)
                node.add(new DefaultMutableTreeNode(fc));
            root.add(node);
        }
        return root;
    }
    public DefaultMutableTreeNode getArraysTree() {
        DefaultMutableTreeNode root = new DefaultMutableTreeNode(ArrayGraphTitle);
        for (ArrayDecl a : array_decls)
            root.add(new DefaultMutableTreeNode(a));
        return root;
    }
    public boolean UpdateType(FileType type_in) {
        if (!fileType.equals(type_in)) {
            fileType = type_in;
            try {
                father.db.Update(this);
                return true;
            } catch (Exception e) {
                Utils_.MainLog.PrintException(e);
            }
        }
        return false;
    }
    public boolean UpdateLanguage(LanguageName lang_in) {
        if (!languageName.equals(lang_in)) {
            languageName = lang_in;
            try {
                father.db.Update(this);
                return true;
            } catch (Exception e) {
                Utils_.MainLog.PrintException(e);
            }
        }
        return false;
    }
    public boolean UpdateStyle(LanguageStyle s_in) {
        if (!style.equals(s_in)) {
            style = s_in;
            try {
                father.db.Update(this);
                return true;
            } catch (Exception e) {
                Utils_.MainLog.PrintException(e);
            }
        }
        return false;
    }
    public int getFirstBadLine() {
        for (MessageError error : father.db.errors.Data.values())
            if (error.file.equals(this.name)) return error.line;
        return -1;
    }
    public int FragmentLoopCount(int first, int second) {
        int res = 0;
        for (Loop l : AllLoops.values()) {
            if ((l.line >= first) && (l.line <= second))
                res++;
        }
        return res;
    }
    public int FragmentFunctionDeclsCount(int first, int second) {
        int res = 0;
        for (FuncInfo fi : function_decls.values()) {
            if ((fi.line >= first) && (fi.line <= second))
                res++;
        }
        return res;
    }
    public int FragmentFunctionCallsCount(int first, int second) {
        int res = 0;
        for (FuncInfo fi : function_decls.values())
            for (FuncCall fc : fi.callsFrom) {
                if ((fc.line >= first) && (fc.line <= second))
                    res++;
            }
        return res;
    }
    public FuncCall find_current_func_call() {
        //-------------
        for (FuncInfo fi : function_decls.values()) {
            for (FuncCall fc : fi.callsFrom) {
                if ((fc.line == form.getEditor().getCurrentLine()) &&
                        !Global.mainModule.getSapfor().isIntrinsic(fc.funcName)
                ) {
                    return fc;
                }
            }
        }
        //-------------
        return null;
    }
    public FuncCall find_func_call(String funcName) {
        for (FuncInfo fi : function_decls.values()) {
            for (FuncCall fc : fi.callsFrom) {
                if (fc.funcName.equalsIgnoreCase(funcName) &&
                        (fc.line == form.getEditor().getCurrentLine()
                        )) {
                    return fc;
                }
            }
        }
        return null;
    }
    public Loop find_current_loop() {
        int line = form.getEditor().getCurrentLine();
        return AllLoops.getOrDefault(line, null);
    }
    public void Exclude() throws Exception {
        state = FileState.Excluded;
        father.db.Update(this);
    }
    public void Include() throws Exception {
        state = FileState.Undefined;
        father.db.Update(this);
    }
    public String getUnixName() {
        return Utils_.toU(name);
    }
    public String getQObjectName() {
        return Utils_.DQuotes(getUnixName() + ".o");
    }
    @Override
    public String toString() {
        return name;
    }
    public String getProjectNameWithoutExtension() {
        String extension = Utils_.getExtension(file);
        return name.substring(0, name.length() - (extension.length() + 1));
    }
    public void importSettings(DBProjectFile parent, boolean sapforStyle) throws Exception {
        fileType = parent.fileType;
        switch (parent.state) {
            case Undefined:
            case Excluded:
                state = parent.state;
                break;
            default:
                state = FileState.Undefined;
                break;
        }
        languageName = parent.languageName;
        if (sapforStyle)
            style = Global.mainModule.getProject().sapforProperties.FREE_FORM ? LanguageStyle.free : LanguageStyle.fixed;
        else style = parent.style;
    }
    public void importSourceCodeSettings(DBProjectFile parent, boolean sapforStyle) throws Exception {
        switch (parent.state) {
            case Undefined:
            case Excluded:
                state = parent.state;
                break;
            default:
                state = FileState.Undefined;
                break;
        }
        languageName = parent.languageName;
        if (sapforStyle)
            style = Global.mainModule.getProject().sapforProperties.FREE_FORM ? LanguageStyle.free : LanguageStyle.fixed;
        else style = parent.style;
    }
    //------------------
    public void CheckStateByMessageType(int m_type) throws Exception {
        switch (m_type) {
            case 0:
                if (state != FileState.HasErrors) state = FileState.HasWarnings;
                break;
            case 1:
                state = FileState.HasErrors;
                break;
            case 2:
                if (state != FileState.HasWarnings && state != FileState.HasErrors) state = FileState.HasNotes;
                break;
        }
    }
    //------------------
    public void ReadMessagesNoSave(String text, LinkedHashMap<String, DBProjectFile> files) throws Exception {
        String[] nw = text.split("\n");
        for (String S : nw) {
            Pair<Integer, String> p = decodeParserMessage(S, name);
            if (S.toLowerCase().startsWith("note")) {
                files.get(p.getValue()).CheckStateByMessageType(2);
            } else if (S.toLowerCase().startsWith("warning")) {
                files.get(p.getValue()).CheckStateByMessageType(0);
            } else if (S.toLowerCase().startsWith("error"))
                files.get(p.getValue()).CheckStateByMessageType(1);
        }
    }
    public void ReadParseMessagesNoSave(LinkedHashMap<String, DBProjectFile> files) throws Exception {
        if (getParserOutFile().exists()) ReadMessagesNoSave(Utils.ReadAllText(getParserOutFile()), files);
        if (getParserErrFile().exists()) ReadMessagesNoSave(Utils.ReadAllText(getParserErrFile()), files);
    }
    public int FragmentArraysCount(Integer first, Integer second) {
        int res = 0;
        for (ArrayDecl ai : array_decls) {
            if ((ai.line >= first) && (ai.line <= second))
                res++;
        }
        return res;
    }
    //---
    public void saveLoops(List<Loop> loopNests_in) {
        AllLoops.clear();
        LoopNests.clear();
        //--
        LoopNests.addAll(loopNests_in);
        for (Loop loop : LoopNests) {
            loop.setFile(this.name);
            loop.toMap_r(AllLoops);
        }
        LoopGraphTitle = "Всего циклов: " + AllLoops.size();
    }
    public void saveFunctions(List<FuncInfo> funcInfos_in) throws Exception {
        function_decls.clear();
        int call_count = 0;
        //--
        for (FuncInfo funcInfo : funcInfos_in) {
            //--
            funcInfo.file = this.name;
            funcInfo.ownCalls = new LinkedHashMap<>();
            funcInfo.CheckMessagesPresence();
            //--
            for (FuncCall call : funcInfo.callsFrom) {
                call.file = this.name;
                call.parentLineOffset = funcInfo.line - call.line;
                call.CheckMessagesPresence();
            }
            if (funcInfo.isMain != 0) {
                isMain = 1;
                funcInfo.type = FunctionType.Main;
                father.main_function = funcInfo;
                father.db.Update(this);
            } else {
                funcInfo.type = FunctionType.Default;
            }
            //--
            function_decls.put(funcInfo.funcName, funcInfo);
            call_count += funcInfo.callsFrom.size();
        }
        //--
        CallGraphTitle = "Объявлений : " + function_decls.size() + "; Вызовов : " + call_count;
    }
}