package Common.Passes;
import Common.MainModule_;
import Common.Utils.Stopwatch;
import Common.Utils.TextLog;
import Common.Utils.Utils_;
import Common.Visual.Controls.PassButton;
import Common.Visual.Controls.PassControl;
import Common.Visual.Controls.StablePassMenuItem;
import Common.Visual.UI;
import Common.Visual.Windows.PassForm;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.util.Stack;
import java.util.Vector;
import java.util.concurrent.Semaphore;
public class Pass<T> {
    public int callsCount = 0; //число вызовов прохода за текущий сеанс визуализатора.
    public T target; //главный аргумент.
    public PassState state = PassState.Inactive; //текущее состояние прохода.
    public Semaphore animation_sem;
    //--------------
    public boolean ui_visible = true;
    public Vector<PassControl> controls = new Vector<>();
    public TextLog Log; //внутренний журнал прохода.
    protected boolean interrupt;
    protected SwingWorker dispatcher = null;
    protected JMenuItem menuItem = null;
    private PassForm form = null;
    private JButton button = null;
    private JButton tabButton = null;
    private Exception last_error; //последнее пойманное исключение выполнения.
    public void addControl(PassControl control_in) {
        control_in.setVisible(ui_visible);
        controls.add(control_in);
    }
    //->>
    public Throwable getCauseRec(Throwable ex) {
        Throwable cause = ex.getCause();
        return (cause == null) ? ex : getCauseRec(cause);
    }
    //<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) {
        ui_visible = flag;
        for (PassControl control : controls)
            control.setVisible(ui_visible);
    }
    public void setControlsToolTipText(String text) {
        for (PassControl control : controls)
            control.setToolTipText(text);
    }
    protected void FocusResult() {
    }
    public boolean hasStats() {
        return false;
    }
    public void UpdateStatsIfNeed() {
        if (hasStats()) {
            try {
                MainModule_.instance.getDb().passStats.IncPassStat(getName());
            } catch (Exception ex) {
                Utils_.MainLog.PrintException(ex);
            }
            if (UI.isActive())
                MainModule_.instance.getUI().getFastAccessMenuBar().Refresh();
        }
    }
    public boolean isDone() {
        return state.equals(PassState.Done);
    }
    public void setDone() {
        state = PassState.Done;
    }
    public String getName() {
        return getClass().getSimpleName();
    }
    public String getDescription() {
        return MainModule_.instance.getPassDescription(getName());
    }
    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 (UI.isActive())
            showCanNotStart();
    }
    protected void PerformPreparation() throws Exception {
        performPreparation();
        if (UI.isActive())
            showPreparation();
    }
    private void PerformFinish() throws Exception {
        performFinish();
        //-
        if (UI.isActive())
            showFinish();
    }
    private void PerformDone() throws Exception {
        performDone();
        //-
        if (UI.isActive())
            showDone();
    }
    private void PerformFail() throws Exception {
        performFail();
        //-
        if (UI.isActive())
            showFail();
    }
    //------
    protected PassCode_ necessary() {
        return null;
    }
    protected boolean resetsNecessary() {
        return false;
    }
    public void Reset() {
        if (state == PassState.Done)
            state = PassState.Inactive;
    }
    //------
    protected void createStack_r(Stack<Pass> ToDo, Vector<String> ToPrint) {
        ToDo.push(this);
        if (needsConfirmations() // если UI нет, сюда он не дойдет.
        ) {
            ToPrint.add(this.getStartDescription());
        }
        if (necessary() != null) {
            Pass next = MainModule_.instance.getPass(necessary());
            if (resetsNecessary() || !next.isDone())
                next.createStack_r(ToDo, ToPrint);
        }
    }
    public boolean Do(Object... args) {
        Stack<Pass> ToDo = new Stack<>();
        Vector<String> ToPrint = new Vector<>();
        createStack_r(ToDo, ToPrint);
        if (
                UI.isActive() && MainModule_.instance.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 (UI.isActive() && MainModule_.instance.focusPassesResult())
                FocusResult();
            //-
            if (UI.isActive() && MainModule_.instance.confirmPassesDone() && !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
            Utils_.MainLog.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())
            Utils_.MainLog.Print(getDescription() + " стартует..");
        timer.Start();
        //------------------------------->
        try {
            if (UI.isActive()) FocusBeforeStart();
            if (canStart(args)) {
                PerformPreparation();
                if (UI.isActive() && needsAnimation()) {
                    animation_sem = new Semaphore(1);
                    animation_sem.acquire();
                    //---
                    form = null;
                    MainModule_.instance.getUI().DropPassForm();
                    System.gc();
                    //--
                    form = MainModule_.instance.getUI().SetPassForm(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) {
                                Utils_.MainLog.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);
                    }
                }
                PerformFinish();
                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;
            Utils_.MainLog.PrintException(ex);
        }
        //------------------------------->
        timer.Stop();
        if (!getDescription().isEmpty()) {
            Utils_.MainLog.Print(
                    getDescription() +
                            " окончен за " + timer.Print() +
                            " состояние " + Utils_.Brackets(state.getDescription())
            );
            if (!Log.isEmpty() && UI.isActive())
                UI.Error(
                        "проход " + Utils_.Brackets(getDescription()) + "\n" + state.getDescription() + "\n\n" +
                                Log.toString());
        }
        //------------------------------->
        return isDone();
    }
    //--
    public void ShowSapforMessage(String message) {
        if (UI.isActive() && 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;
            }
        }
    }
    public void ShowMessage1(String message) {
        if (form != null)
            form.fields.Label1.setText((message.length() > 1) ? message : "");
    }
    public void ShowMessage2(String message) {
        if (form != null)
            form.fields.Label2.setText((message.isEmpty() ? "" : 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);
    }
}
