package Visual_DVM_2021.UI.Main;
import Common.Constants;
import Common.Current;
import Common.Database.DBObject;
import Common.Global;
import Common.UI.Editor.BaseEditor;
import Common.UI.Label.ShortLabel;
import Common.UI.Menus_2023.VisualiserMenuBar;
import Common.UI.UI;
import Common.Utils.TextLog;
import Common.Utils.Utils;
import GlobalData.Settings.SettingName;
import Visual_DVM_2021.Passes.Pass_2021;
import javafx.util.Pair;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaHighlighter;
import org.fife.ui.rtextarea.RTextScrollPane;

import javax.swing.*;
import java.util.LinkedHashMap;
import java.util.Vector;
public abstract class ComparisonForm<T> {
    public Class<T> t; //класс объектов.
    //-->>
    private JPanel content;
    public JPanel getContent() {
        return content;
    }
    protected JToolBar tools;
    private JPanel editorPanel;
    protected JLabel lObjectName;
    protected JButton bApplyObject;
    private JButton bPrevious;
    private JButton bNext;
    private JButton bCompare;
    protected JButton bClose;
    //-->>
    ComparisonForm<T> this_ = null; //?
    ComparisonForm<T> slave = null;
    ComparisonForm<T> master = null;
    //-->>
    protected T object = null;
    //-->>
    protected BaseEditor Body;
    private RTextScrollPane Scroll;
    //-->>
    public Vector<String> lines = new Vector<>(); //строки с учетом/неучетом пробелов. для сравнения
    public Vector<String> visible_lines = new Vector<>(); //строки с нетронутыми пробелами. для отображения
    //подсветка.
    public LinkedHashMap<Integer, Pair<Integer, Boolean>> colors = new LinkedHashMap<>();
    public RSyntaxTextAreaHighlighter slave_highlighter = null; //погонщик рабов
    //-----
    private boolean events_on = false;//относится только к мастеру, отвечает за скроллы.
    private int current_diff_line = -1;
    //--->>
    public boolean isMaster() {
        return slave != null;
    }
    public boolean isSlave() {
        return master != null;
    }
    //--->>
    //неперегружаемые методы
    protected void RemoveObject() {
        object = null;
        removeObject();
        showNoObject();
        ClearText();
        /*
        if (isMaster())
            slave.ClearText();
        else if (isSlave())
            master.ClearText();
         */
    }
    public void ApplyObject() {
        RemoveObject();
        TextLog log = new TextLog();
        if (Current.Check(log, getCurrentObjectName())) {
            object = (T) Current.get(getCurrentObjectName());
            applyObject();
            showObject();
        } else
            UI.Info(log.toString());
    }
    private void ShowCurrentDiff() {
        Body.gotoLine_(colors.get(current_diff_line).getKey());
    }
    public static boolean CompareLines(String line1_raw, String line2_raw) {
        String line1 = line1_raw;
        String line2 = line2_raw;
        if (!Global.db.settings.get(SettingName.RegisterOn).toBoolean()) {
            line1 = line1.toUpperCase();
            line2 = line2.toUpperCase();
        }
        if (!Global.db.settings.get(SettingName.SpacesOn).toBoolean()) {
            line1 = Utils.remove(line1, " ", "\t");
            line2 = Utils.remove(line2, " ", "\t");
        }
        return line1.equals(line2);
    }
    public static boolean Contains(Vector<String> list, String line, int max_index) {
        int last_index = -1;
        for (int i = 0; i < list.size(); ++i)
            if (CompareLines(list.get(i), line)) last_index = i;
        return (last_index >= max_index);
    }
    private void getLines() {
        lines.clear();
        visible_lines.clear();
        //1.прочитать весь текст.
        char[] chars = getText().toCharArray();
        //по символам получить строки.
        char c = 0; //текущий символ
        int i = 0; //индекс текущего символа.
        StringBuilder line = new StringBuilder(); //текущая строка
        StringBuilder v_line = new StringBuilder(); //текущая строка
        while (i < chars.length) {
            c = chars[i];
            //System.out.print("`"+c+"`");
            ++i;
            switch (c) {
                case '\r': //возврат каретки, игнор
                    break;
                case ' ':
                case '\t':
                    if (Global.db.settings.get(SettingName.SpacesOn).toBoolean()) line.append(c);
                    v_line.append(c);
                    break;
                case '\n': //конец строки
                    if (fortranWrapsOn()) {
                        //оракул. лезем в начало следующей строки
                        //и анализируем первые 5 символов
                        boolean hasWrap = false;
                        int wi;
                        //------
                        //System.out.println("checking wrap...");
                        //с нуля потому что и уже увеличено.
                        for (int j = 0; (j < 6) && ((wi = i + j) < chars.length); ++j) {
                            char s = chars[wi];
                            // System.out.print(s);
                            if ((j == 0) && ((s == 'c') || (s == 'C') || (s == '!'))) {
                                // System.out.println("next line is FL comment");
                                break;
                            }
                            if ((j > 0) && (j < 5) && (s == '!')) {
                                // System.out.println("next line is comment");
                                break;
                            }
                            if ((j == 5) && (s != ' ')) {
                                hasWrap = true;
                                i = wi + 1;
                                // System.out.println("next line is WRAP");
                                break;
                            }
                        }
                        // System.out.println();
                        //-----
                        if (hasWrap)
                            break;
                    }
                    // System.out.println();
                    //добавление строки в результат.
                    if ((line.length() > 0) || Global.db.settings.get(SettingName.EmptyLinesOn).toBoolean() &&
                            Global.db.settings.get(SettingName.SpacesOn).toBoolean()) {
                        lines.add(line.toString());
                        visible_lines.add(v_line.toString());
                    }
                    //сброс
                    line = new StringBuilder();
                    v_line = new StringBuilder();
                    break;
                default:
                    line.append(c);
                    v_line.append(c);
                    break;
            }
        }
        if ((i > 0) && (c != '\n')) {
            //строка оборвалась на EOF
            //добавление строки в результат.
            if ((line.length() > 0) || Global.db.settings.get(SettingName.EmptyLinesOn).toBoolean() && Global.db.settings.get(SettingName.SpacesOn).toBoolean()) {
                lines.add(line.toString());
                visible_lines.add(v_line.toString());
            }
        }
    }
    protected void ClearText() {
        events_on = false;
        Body.setText("объект не назначен");
    }
    //предполагаем что оба объекта есть и мы можем получить с них текст.
    protected void Compare() throws Exception {
        events_on = false;
        current_diff_line = Constants.Nan;
        colors.clear();
        //-----------------------------------------------------------------------------------------------
        Body.setText("");
        slave.Body.setText("");
        int d = 0;
        getLines();
        slave.getLines();
        //--------------------------------------------------------------------
        Vector<String> t1 = new Vector<>();
        Vector<String> t2 = new Vector<>();
        //------
        int old_j = 0;
        int j = 0;
        for (int i = 0; i < lines.size(); ++i) {
            if (Contains(slave.lines, lines.get(i), old_j)) {
                for (int k = old_j; k < slave.lines.size(); ++k) {
                    j = k;
                    if (CompareLines(lines.get(i), slave.lines.get(k))) {
                        j++;
                        t1.add(visible_lines.get(i));
                        t2.add(slave.visible_lines.get(k));
                        break;
                    } else {
                        t1.add("+");
                        t2.add("+ " + slave.visible_lines.get(k));
                        colors.put(d, new Pair(t2.size() - 1, true));
                        ++d;
                    }
                }
                old_j = j;
            } else {
                //строки гарантированно нет.
                t1.add("- " + visible_lines.get(i));
                t2.add("- " + visible_lines.get(i));
                colors.put(d, new Pair(t2.size() - 1, false));
                ++d;
            }
        }
        //теперь граничное условие. если первый файл кончился а второй нет, его остаток это добавление.
        for (int i = j; i < slave.lines.size(); ++i) {
            t1.add("+");
            t2.add("+ " + slave.visible_lines.get(i));
            colors.put(d, new Pair(t2.size() - 1, true));
            ++d;
        }
        ///----------------
        Body.setText(String.join("\n", t1));
        slave.Body.setText(String.join("\n", t2));
        Body.setCaretPosition(0);
        slave.Body.setCaretPosition(0);
        //теперь покрас.
        for (Integer diff_num : colors.keySet()) {
            slave_highlighter.addHighlight(
                    slave.Body.getLineStartOffset(colors.get(diff_num).getKey()),
                    slave.Body.getLineEndOffset(colors.get(diff_num).getKey()),
                    colors.get(diff_num).getValue() ? UI.GoodLoopPainter : UI.BadLoopPainter
            );
        }
        if (colors.size() > 0) current_diff_line = 0;
        events_on = true;
    }
    public void Show() throws Exception {
        events_on = false;
        current_diff_line = Constants.Nan;
        colors.clear();
        //----------------------------------------------------------------------------------------------
        Body.setText("");
        slave.Body.setText("");
        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 (lines.size() > slave.lines.size()) {
            Utils.addEmptyLines(t2, delta);
        } else if (lines.size() < slave.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 abstract Current getCurrentObjectName();
    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;
    }
    //--->>
    //   protected Object ownScrollModel = null;
    //---<<
    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;
        bPrevious.setVisible(isMaster());
        bNext.setVisible(isMaster());
        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());
            slave_highlighter = (RSyntaxTextAreaHighlighter) slave.Body.getHighlighter();
            //бяк быть не должно при условии что строк одинаковое количество. а это должно выполняться.
            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) {
                        Global.Log.PrintException(ex);
                    }
                }
            });
            //</editor-fold>
            slave.master = this;
            bPrevious.addActionListener(e -> {
                if (current_diff_line != Constants.Nan) {
                    if (current_diff_line > 0)
                        current_diff_line--;
                    else
                        current_diff_line = colors.size() - 1;
                    ShowCurrentDiff();
                }
            });
            bNext.addActionListener(e -> {
                if (current_diff_line != Constants.Nan) {
                    if (current_diff_line < colors.size() - 1)
                        current_diff_line++;
                    else
                        current_diff_line = 0;
                    ShowCurrentDiff();
                }
            });
            bCompare.addActionListener(e -> {
                DoComparePass(isReady() && slave.isReady());
            });
        } else {
            //рабу сравнивать не положено.
            bCompare.setVisible(false);
        }
        //--->>>
        bClose.addActionListener(e -> {
            onClose();
        });
    }
    //-->>
    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_2021 pass = new Pass_2021() {
            @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_2021 pass = new Pass_2021() {
            @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();
    }
}
