package Visual_DVM_2021.Passes;
import Common.Current;
import Common.Global;
import Common.UI.DebugPrintLevel;
import Common.UI.Menus_2023.PassButton;
import Common.UI.Menus_2023.PassControl;
import Common.UI.Menus_2023.StablePassMenuItem;
import Common.UI.UI;
import Common.Utils.Stopwatch;
import Common.Utils.TextLog;
import Common.Utils.Utils;
import Visual_DVM_2021.PassStats.PassStats;
import Visual_DVM_2021.Passes.UI.PassForm;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.util.LinkedHashMap;
import java.util.Stack;
import java.util.Vector;
import java.util.concurrent.Semaphore;
public class Pass_2021<T> {
    public static Vector<Pass_2021> FAPasses = new Vector<>();
    //</editor-fold>
    //-
    public static LinkedHashMap<PassCode_2021, Pass_2021> passes = new LinkedHashMap<>();
    public PassStats stats = null;
    public int callsCount = 0; //число вызовов прохода за текущий сеанс визуализатора.
    public T target; //главный аргумент.
    public PassState state = PassState.Inactive; //текущее состояние прохода.
    public Semaphore animation_sem;
    //--------------
    public Vector<PassControl> controls = new Vector<>();
    protected TextLog Log; //внутренний журнал прохода.
    protected boolean interrupt;
    protected SwingWorker dispatcher = null;
    private PassForm form = null;
    protected JMenuItem menuItem = null;
    private JButton button = null;
    private JButton tabButton = null;
    private Exception last_error; //последнее пойманное исключение выполнения.
    //->>
    public static Throwable getCauseRec(Throwable ex) {
        Throwable cause = ex.getCause();
        return (cause == null) ? ex : getCauseRec(cause);
    }
    public static void setPassesControlsVisible(boolean flag, PassCode_2021... codes_in) {
        for (PassCode_2021 code_in : codes_in)
            passes.get(code_in).setControlsVisible(flag);
    }
    //важно. вызывать только если есть интерфейс.
    public static void CheckAllStats() throws Exception {
        for (Pass_2021 pass : FAPasses) {
            if (!Global.db.passStats.Data.containsKey(pass.code()))
                Global.db.Insert(pass.stats = new PassStats(pass));
            else pass.stats = Global.db.passStats.Data.get(pass.code());
        }
        FAPasses.sort(new SortPassesByStats());
    }
    public static void CreateAll() {
        for (PassCode_2021 code : PassCode_2021.values()) {
            if (code != PassCode_2021.Undefined) {
                try {
                    Class<?> clazz = Class.forName("Visual_DVM_2021.Passes.All." + code.toString());
                    Pass_2021 pass = ((Pass_2021) clazz.newInstance());
                    passes.put(code, pass);
                    if (pass.hasStats())
                        FAPasses.add(pass);
                } catch (Exception ex) {
                    Global.Log.PrintException(ex);
                }
            }
        }
    }
    //<editor-fold desc="Интерфейс">
    //https://www.delftstack.com/ru/howto/java/java-resize-image/
    public String getIconPath() {
        return null;
    }
    public Icon getTabIcon(){
        return Utils.getTabIcon(getIconPath());
    }
    public AbstractAction getControlAction() {
        return new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                UpdateStatsIfNeed();
                Do();
            }
        };
    }
    public String getButtonText() {
        return getDescription();
    }
    public JMenuItem createMenuItem() {
        if (menuItem == null)
            menuItem = new StablePassMenuItem(this);
        return menuItem;
    }
    public JButton createButton() {
        if (button == null)
            button = new PassButton(this);
        return button;
    }
    public JButton createTabButton() {
        if (tabButton == null)
            tabButton = new PassButton(this, true);
        return tabButton;
    }
    public void setControlsEnabled(boolean flag) {
        for (PassControl control : controls)
            control.setEnabled(flag);
    }
    public void setControlsVisible(boolean flag) {
        for (PassControl control : controls)
            control.setVisible(flag);
    }
    public void setControlsToolTipText(String text) {
        for (PassControl control : controls)
            control.setToolTipText(text);
    }
    protected void FocusResult() {
    }
    protected boolean hasStats() {
        return false;
    }
    public void UpdateStatsIfNeed() {
        if (hasStats()) {
            stats.Inc();
            try {
                Global.db.Update(stats);
                FAPasses.sort(new SortPassesByStats());
                if (Current.HasProject())
                    UI.fastAccessMenuBar.Refresh();
            } catch (Exception ex) {
                Global.Log.PrintException(ex);
            }
        }
    }
    public PassCode_2021 code() {
        return PassCode_2021.valueOf(getClass().getSimpleName());
    }
    public boolean isDone() {
        return state == PassState.Done;
    }
    public void setDone() {
        state = PassState.Done;
    }
    public String getDescription() {
        return code().getDescription();
    }
    public String getStartDescription() {
        return getDescription();
    }
    protected void FocusBeforeStart() {
        //отображение вкладок.
    }
    //-
    //объединение старых методов InitArgs и CanStart. зачастую семантика у них пересекалась.
    protected boolean canStart(Object... args) throws Exception {
        return true;
    }
    //-
    protected void body() throws Exception {
    }
    protected boolean validate() {
        return true;
    }
    //-
    protected void performCanNotStart() throws Exception {
    }
    protected void showCanNotStart() throws Exception {
    }
    protected void performPreparation() throws Exception {
    }
    protected void showPreparation() throws Exception {
    }
    protected void performFinish() throws Exception {
    }
    protected void showFinish() throws Exception {
    }
    protected void performDone() throws Exception {
    }
    protected void showDone() throws Exception {
    }
    protected void performFail() throws Exception {
    }
    protected void showFail() throws Exception {
    }
    //-
    private void PerformCanNotStart() throws Exception {
        performCanNotStart();
        if (Current.hasUI())
            showCanNotStart();
    }
    protected void PerformPreparation() throws Exception {
        performPreparation();
        if (Current.hasUI())
            showPreparation();
    }
    private void PerformFinish() throws Exception {
        performFinish();
        //-
        if (Current.hasUI())
            showFinish();
    }
    private void PerformDone() throws Exception {
        performDone();
        //-
        if (Current.hasUI())
            showDone();
    }
    private void PerformFail() throws Exception {
        performFail();
        //-
        if (Current.hasUI())
            showFail();
    }
    //------
    protected PassCode_2021 necessary() {
        return null;
    }
    protected boolean resetsNecessary() {
        return false;
    }
    public void Reset() {
        if (state == PassState.Done)
            state = PassState.Inactive;
    }
    //------
    protected void createStack_r(Stack<Pass_2021> ToDo, Vector<String> ToPrint) {
        ToDo.push(this);
        if (needsConfirmations() // если UI нет, сюда он не дойдет.
        ) {
            ToPrint.add(this.getStartDescription());
        }
        if (necessary() != null) {
            Pass_2021 next = passes.get(necessary());
            if (resetsNecessary() || !next.isDone())
                next.createStack_r(ToDo, ToPrint);
        }
    }
    public boolean Do(Object... args) {
        Stack<Pass_2021> ToDo = new Stack<>();
        Vector<String> ToPrint = new Vector<>();
        createStack_r(ToDo, ToPrint);
        if (Global.properties.ConfirmPassesStart && !ToPrint.isEmpty() &&
                !UI.Question("Выполнить проход(ы):\n" + String.join("\n", ToPrint))
        ) return false;
        while (ToDo.size() > 1) {
            if (!ToDo.pop().start()) return false;
        }
        if (start(args)) {
            if (Global.properties.FocusPassesResult)
                FocusResult();
            //-
            if (Global.properties.ShowPassesDone && !ToPrint.isEmpty()
            ) {
                UI.Info("Проход(ы)\n\n" + String.join("\n", ToPrint) +
                        "\nуспешно выполнен(ы)!");
            }
            //-
            return true;
        }
        return false;
    }
    //-
    //<editor-fold desc="Анимация">
    protected boolean needsAnimation() {
        return false;
    }
    public void Interrupt() throws Exception {
        UI.Info("Проход " + Utils.Brackets(getDescription()) + " не разрешено прерывать.");
    }
    public boolean needsConfirmations() {
        return false;
    }
    //</editor-fold>
    //-
    protected void CheckException(Exception ex) {
        if (ex instanceof PassException) {
            Log.Writeln_(ex.getMessage());
        } else
            Global.Log.PrintException(ex);
    }
    public boolean start(Object... args) {
        //------------------------------->
        interrupt = false;
        callsCount++;
        Stopwatch timer = new Stopwatch();
        state = PassState.Inactive;
        last_error = null;
        target = null;
        Log = new TextLog();
        if (!getDescription().isEmpty())
            Global.Log.Print(DebugPrintLevel.Passes, getDescription() + " стартует..");
        timer.Start();
        //------------------------------->
        try {
            if (Current.hasUI()) FocusBeforeStart();
            if (canStart(args)) {
                PerformPreparation();
                //todo тут должна быть вилка на анимацию?
                if (Current.hasUI() && needsAnimation()) {
                    animation_sem = new Semaphore(1);
                    animation_sem.acquire();
                    //---
                    form = null;
                    Current.set(Current.PassForm, null);
                    System.gc();
                    //--
                    Current.set(Current.PassForm, form = new PassForm(this));
                    dispatcher = new SwingWorker() {
                        @Override
                        protected Object doInBackground() {
                            //  UI.Print(DebugPrintLevel.Passes, "фоновое выполнение началось");
                            try {
                                body();
                            } catch (Exception ex) {
                                last_error = ex;
                            }
                            //  UI.Print(DebugPrintLevel.Passes, "Ожидание активации окна анимации ");
                            //ждем пока окно откроется и освободит его.
                            try {
                                animation_sem.acquire();
                            } catch (InterruptedException e) {
                                Global.Log.PrintException(e);
                            }
                            //и уничтожаем его.
                            //  UI.Print(DebugPrintLevel.Passes, "Окно анимации активировано.");
                            form.dispose();
                            form = null;
                            //   UI.Print(DebugPrintLevel.Passes, "Окно анимации закрыто.");
                            animation_sem.release();
                            return null;
                        }
                        @Override
                        protected void done() {
                            //тут ничего не анализируем. события - отдельный поток. тут это не нужно
                        }
                    };
                    dispatcher.execute();
                    form.ShowDialog();
                    //  UI.Print(DebugPrintLevel.Passes, "фоновое выполнение закончено, обработка резульатов");
                    if (last_error != null) {
                        state = PassState.Crushed;
                        CheckException(last_error);
                    }
                    //  UI.Print(DebugPrintLevel.Passes, "OK");
                    dispatcher = null;
                } else {
                    //исключения во время выполнения возможны в штатном режиме.
                    try {
                        body();
                    } catch (Exception ex) {
                        state = PassState.Crushed;//упал при выполнении.
                        CheckException(ex);
                    }
                }
                //---
                //    UI.Info(this.getClass().getSimpleName()+" +");
                PerformFinish();
                //    UI.Info(this.getClass().getSimpleName()+" ++");
                //---
                if ((state != PassState.Crushed) && validate()) {
                    state = PassState.Done;
                    PerformDone();
                } else {
                    state = PassState.Failed;
                    PerformFail();
                }
            } else {
                state = PassState.CantStart;
                PerformCanNotStart();
            }
        } catch (Exception ex) {
            state = PassState.ExternalCrushed;
            Global.Log.PrintException(ex);
        }
        //------------------------------->
        timer.Stop();
        if (!getDescription().isEmpty()) {
            Global.Log.Print(DebugPrintLevel.Passes,
                    getDescription() +
                            " окончен за " + timer.Print() +
                            " состояние " + Utils.Brackets(state.getDescription())
            );
            if (!Log.isEmpty() && Current.hasUI())
                UI.Error(
                        "проход " + Utils.Brackets(getDescription()) + "\n" + state.getDescription() + "\n\n" +
                                Log.toString());
        }
        //------------------------------->
        return isDone();
    }
    //--
    public void ShowSapforMessage(String message) {
        if (Current.hasUI() && form != null) {
            String[] data = message.split(":");
            switch (data[0]) {
                case "message_1":
                    form.fields.Label1.setText((data.length > 1) ? data[1] : "");
                    break;
                case "message_2":
                    form.fields.Label2.setText((data.length > 1) ? data[1] : "");
                    break;
                case "progress":
                    form.fields.ProgressBar.setValue((data.length > 1) ? Integer.parseInt(data[1]) : 0);
                    break;
            }
        } else System.out.println(Utils.Brackets(message));
    }
    public void ShowMessage1(String message) {
        if (form != null)
            form.fields.Label1.setText((message.length() > 1) ? message : "");
        else System.out.println(Utils.Brackets(message));
    }
    public void ShowMessage2(String message) {
        if (form != null)
            form.fields.Label2.setText((message.isEmpty() ? "" : message));
        else System.out.println(Utils.Brackets(message));
    }
    public void ShowProgress(int total, int current, boolean auto) {
        if (form != null) {
            form.lTitle.setText(getDescription() + " " + (current + 1) + "/" + total);
            if (auto) {
                double dp = ((double) current / total) * 100;
                int p = (int) dp;
                form.fields.ProgressBar.setValue(p);
            }
        }
    }
    public void ShowProgress2(int total, int current, String comment) {
        if (form != null) {
            form.fields.Label2.setText(comment + " " + current + "/" + total);
            double dp = ((double) current / total) * 100;
            int p = (int) dp;
            form.fields.ProgressBar.setValue(p);
        }
    }
    public void ShowProgressTextOnly(int total, int current, String comment) {
        if (form != null)
            form.lTitle.setText(form.lTitle.getText() + " : " + comment + " " + current + "/" + total);
    }
}
