package _VisualDVM.Visual.Windows;
import Common.CommonConstants;
import Common.Database.Objects.DBObject;
import Common.Passes.Pass;
import Common.Utils.Pair;
import Common.Utils.Utils_;
import Common.Visual.Controls.ShortLabel;
import Common.Visual.Editor.BaseEditor;
import Common.Visual.Menus.VisualiserMenuBar;
import Common.Visual.UI;
import _VisualDVM.Global;
import _VisualDVM.ProjectData.Files.UI.Editor.SPFEditor;
import _VisualDVM.Utils;
import com.github.difflib.text.DiffRow;
import com.github.difflib.text.DiffRowGenerator;
import org.fife.ui.rtextarea.RTextScrollPane;

import javax.swing.*;
import javax.swing.text.Highlighter;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;
public abstract class ComparisonForm<T> {
    final String separator = "\u200B";
    final char cseparator = '\u200B';
    public Class<T> t; //класс объектов.
    //-->>
    public Vector<String> visible_lines = new Vector<>(); //строки с нетронутыми пробелами. для отображения
    protected JToolBar tools;
    protected JLabel lObjectName;
    protected JButton bApplyObject;
    protected JButton bClose;
    //-->>
    protected T object = null;
    //-->>
    protected BaseEditor Body;
    //невидимый пробел https://translated.turbopages.org/proxy_u/en-ru.ru.898e1daf-67e318c0-3fccff8a-74722d776562/https/stackoverflow.com/questions/17978720/invisible-characters-ascii
    //--->>
    //   protected Object ownScrollModel = null;
    protected Vector<Pair<Integer, Integer>> diffs = new Vector<>();
    //-->>
    ComparisonForm<T> this_ = null; //?
    ComparisonForm<T> slave = null;
    ComparisonForm<T> master = null;
    //-->>
    private JPanel content;
    private JPanel editorPanel;
    private JButton bCompare;
    private JButton bPrevious;
    private JButton bNext;
    private RTextScrollPane Scroll;
    //-----
    private boolean events_on = false;//относится только к мастеру, отвечает за скроллы.
    private int current_diff_num = -1;
    public ComparisonForm(Class<T> t_in, ComparisonForm<T> slave_in) {
        //-
        Body = new BaseEditor();
        Scroll = new RTextScrollPane(Body);
        editorPanel.add(Scroll);
        //   ownScrollModel = Scroll.getVerticalScrollBar().getModel();
        //-
        t = t_in;
        this_ = this;
        slave = slave_in;
        Scroll.setLineNumbersEnabled(true);
        bApplyObject.addActionListener(e -> {
            ApplyObject();
        });
        //--->>>
        Body.setWhitespaceVisible(true);
        Body.setEditable(false);
        if (isMaster()) {
            //<editor-fold desc="синхронизация скроллов">
            slave.Scroll.getVerticalScrollBar().setModel(Scroll.getVerticalScrollBar().getModel());
            //бяк быть не должно при условии что строк одинаковое количество. а это должно выполняться.
            Body.addCaretListener(e -> {
                if (events_on && isReady() && slave.isReady()) {
                    try {
                        int master_lineNum = Body.getCaretLineNumber();
                        slave.Body.setCaretPosition(slave.Body.getLineStartOffset(master_lineNum));
                    } catch (Exception ex) {
                        Utils_.MainLog.PrintException(ex);
                    }
                }
            });
            //</editor-fold>
            slave.master = this;
            bCompare.addActionListener(e -> {
                DoComparePass(isReady() && slave.isReady());
            });
        } else {
            //рабу сравнивать не положено.
            bCompare.setVisible(false);
        }
        //--->>>
        bClose.addActionListener(e -> {
            onClose();
        });
        bPrevious.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (current_diff_num != CommonConstants.Nan) {
                    if (current_diff_num > 0)
                        current_diff_num--;
                    else
                        current_diff_num = diffs.size() - 1;
                    ShowCurrentDiff();
                }
            }
        });
        bNext.addActionListener(e -> {
            if (current_diff_num != CommonConstants.Nan) {
                if (current_diff_num < diffs.size() - 1)
                    current_diff_num++;
                else
                    current_diff_num = 0;
                ShowCurrentDiff();
            }
        });
    }
    //---<<
    private void ShowCurrentDiff() {
        try {
            int diff_line = Body.getLineOfOffset(diffs.get(current_diff_num).getKey());
            Body.gotoLine_(diff_line);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    public JPanel getContent() {
        return content;
    }
    //--->>
    public boolean isMaster() {
        return slave != null;
    }
    public boolean isSlave() {
        return master != null;
    }
    //--->>
    //неперегружаемые методы
    protected void RemoveObject() {
        object = null;
        removeObject();
        showNoObject();
        ClearText();
    }
    public abstract Object getDefaultCurrentObject();
    public void ApplyObject() {
        if (getDefaultCurrentObject() != null)
            ApplyObject(getDefaultCurrentObject());
        else UI.Info("Текущий объект для сравнения не назначен!");
    }
    public void ApplyObject(Object object_in) {
        RemoveObject();
        object = (T) object_in;
        applyObject();
        showObject();
    }
    private void getLines() {
        visible_lines.clear();
        visible_lines = new Vector<String>(Arrays.asList(getText().split("\n")));
    }
    protected void ClearText() {
        events_on = false;
        Body.setText("объект не назначен");
    }
    //предполагаем что оба объекта есть и мы можем получить с них текст.
    protected void Compare() throws Exception {
        events_on = false;
        current_diff_num = CommonConstants.Nan;
        slave.current_diff_num = CommonConstants.Nan;
        //-----------------------------------------------------------------------------------------------
        Body.setText("");
        slave.Body.setText("");
        Body.getHighlighter().removeAllHighlights();
        slave.Body.getHighlighter().removeAllHighlights();
        getLines();
        slave.getLines();
        //--------------------------------------------------------------------
        Vector<String> t1 = new Vector<>();
        Vector<String> t2 = new Vector<>();
        //------
        DiffRowGenerator generator = DiffRowGenerator.create()
                .showInlineDiffs(true)
                .inlineDiffByWord(true)
                .ignoreWhiteSpaces(!Global.normalProperties.SpacesOn)
                .oldTag(f -> separator)
                .newTag(f -> separator)
                .build();
        List<DiffRow> rows = generator.generateDiffRows(
                visible_lines,
                slave.visible_lines);
        for (DiffRow row : rows) {
            t1.add(row.getOldLine());
            t2.add(row.getNewLine());
        }
        Body.setText(String.join("\n", t1));
        slave.Body.setText(String.join("\n", t2));
        Body.setCaretPosition(0);
        slave.Body.setCaretPosition(0);
        //--
        fillDiffs();
        slave.fillDiffs();
        colorDiffs();
        slave.colorDiffs();
        events_on = true;
    }
    public void fillDiffs() {
        diffs.clear();
        boolean flag = false;
        char[] chars = Body.getText().toCharArray();
        int dif_start = CommonConstants.Nan;
        int dif_end = CommonConstants.Nan;
        for (int i = 0; i < chars.length; ++i) {
            char c = chars[i];
            //--
            if (flag) {
                //различие
                switch (c) {
                    case cseparator:
                        //кончилось различие
                        dif_end = i;
                        flag = false;
                        diffs.add(new Pair<>(dif_start, dif_end));
                        dif_start = CommonConstants.Nan;
                        dif_end = CommonConstants.Nan;
                        break;
                }
            } else {
                //поиск
                switch (c) {
                    case cseparator:
                        //началось различие
                        dif_start = i;
                        flag = true;
                        break;
                    default:
                        break;
                }
            }
        }
        if (!diffs.isEmpty())
            current_diff_num = 0;
    }
    public void colorDiffs() throws Exception {
        Highlighter.HighlightPainter painter = isMaster() ? SPFEditor.RedTextPainter : SPFEditor.GreenTextPainter;
        for (Pair<Integer, Integer> diff : diffs) {
            Body.getHighlighter().addHighlight(diff.getKey(), diff.getValue(), painter);
        }
    }
    public void Show() throws Exception {
        events_on = false;
        current_diff_num = CommonConstants.Nan;
        slave.current_diff_num = CommonConstants.Nan;
        //----------------------------------------------------------------------------------------------
        Body.setText("");
        slave.Body.setText("");
        Body.getHighlighter().removeAllHighlights();
        slave.Body.getHighlighter().removeAllHighlights();
        int d = 0;
        getLines();
        slave.getLines();
        //--------------------------------------------------------------------
        Vector<String> t1 = new Vector<>();
        Vector<String> t2 = new Vector<>();
        //------
        t1.addAll(visible_lines);
        t2.addAll(slave.visible_lines);
        //просто выясняем кто из них длиннее, и короткий дополняем пустыми строками.]
        int delta = Math.abs(t1.size() - t2.size());
        if (visible_lines.size() > slave.visible_lines.size()) {
            Utils.addEmptyLines(t2, delta);
        } else if (visible_lines.size() < slave.visible_lines.size()) {
            Utils.addEmptyLines(t1, delta);
        }
        ///----------------
        Body.setText(String.join("\n", t1));
        slave.Body.setText(String.join("\n", t2));
        Body.setCaretPosition(0);
        slave.Body.setCaretPosition(0);
        events_on = true;
    }
    //Перегружаемые методы.
    //--->>
    protected void showNoObject() {
        lObjectName.setText("?");
        lObjectName.setToolTipText("Объект не назначен.");
    }
    protected void showObject() {
        if (object instanceof DBObject) {
            DBObject dbObject = (DBObject) object;
            lObjectName.setText(dbObject.toString());
            lObjectName.setToolTipText(dbObject.toString());
        }
    }
    protected void removeObject() {
    }
    protected void applyObject() {
    }
    protected abstract String getText();
    protected boolean fortranWrapsOn() {
        return false;
    }
    //-->>
    private void createUIComponents() {
        // TODO: place custom component creation code here
        lObjectName = new ShortLabel(40);
        tools = new VisualiserMenuBar();
    }
    //для сравнения по кнопке.
    public boolean isReady() {
        return object != null;
    }
    public void onClose() {
        RemoveObject();
    }
    public void DoComparePass(boolean startCondition) {
        Pass pass = new Pass() {
            @Override
            public String getDescription() {
                return "Сравнение";
            }
            @Override
            protected boolean needsAnimation() {
                return true;
            }
            @Override
            public boolean needsConfirmations() {
                return false;
            }
            @Override
            protected boolean canStart(Object... args) throws Exception {
                return startCondition;
            }
            @Override
            protected void body() throws Exception {
                Compare();
            }
        };
        pass.Do();
    }
    public void DoShowPass(boolean startCondition) {
        Pass pass = new Pass() {
            @Override
            public String getDescription() {
                return "Отображение";
            }
            @Override
            protected boolean needsAnimation() {
                return false;
            }
            @Override
            public boolean needsConfirmations() {
                return false;
            }
            @Override
            protected boolean canStart(Object... args) throws Exception {
                return startCondition;
            }
            @Override
            protected void body() throws Exception {
                Show();
            }
        };
        pass.Do();
    }
}
