package mara.mybox.controller;

import java.io.File;
import java.sql.Connection;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import javafx.animation.FadeTransition;
import javafx.animation.PathTransition;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.Tab;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.Path;
import javafx.stage.Popup;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.Duration;
import mara.mybox.data.ImageItem;
import mara.mybox.data.IntPoint;
import mara.mybox.db.DerbyBase;
import mara.mybox.db.data.VisitHistory;
import mara.mybox.dev.MyBoxLog;
import mara.mybox.fxml.FxSingletonTask;
import mara.mybox.fxml.LocateTools;
import mara.mybox.fxml.menu.MenuTools;
import mara.mybox.fxml.PopTools;
import mara.mybox.fxml.RecentVisitMenu;
import mara.mybox.fxml.SoundTools;
import mara.mybox.fxml.cell.ListImageItemCell;
import mara.mybox.fxml.cell.TableAutoCommitCell;
import mara.mybox.fxml.converter.IntegerStringFromatConverter;
import mara.mybox.fxml.style.NodeStyleTools;
import mara.mybox.tools.DateTools;
import mara.mybox.tools.FileNameTools;
import mara.mybox.tools.HtmlWriteTools;
import mara.mybox.value.AppVariables;
import mara.mybox.value.FileFilters;
import mara.mybox.value.InternalImages;
import static mara.mybox.value.Languages.message;
import mara.mybox.value.UserConfig;

/**
 * @Author Mara
 * @CreateDate 2019-12-30
 * @License Apache License Version 2.0
 */
public class GameEliminationController extends BaseController {

    protected int boardSize, chessWidth, minimumAdjacent, totalScore, autoSpeed,
            flushDuration, eliminateDelay, flushTimes;
    protected Map<String, VBox> chessBoard;
    protected List<Integer> countedChesses;
    protected IntPoint firstClick;
    protected boolean countScore, isEliminating, autoPlaying, stopped;
    protected Random random;
    protected ObservableList<ScoreRuler> scoreRulersData;
    protected Map<Integer, Integer> scoreRulers;
    protected Map<Integer, Integer> scoreRecord;
    protected Date startTime;
    protected Adjacent lastElimination, lastRandom;
    protected String currentStyle, focusStyle, defaultStyle, arcStyle, shadowStyle;
    protected File soundFile;

    @FXML
    protected Tab playTab, chessesTab, rulersTab, settingsTab;
    @FXML
    protected VBox chessboardPane;
    @FXML
    protected Label chessesLabel, scoreLabel, autoLabel, soundFileLabel;
    @FXML
    protected ListView<ImageItem> imagesListview;
    @FXML
    protected FlowPane countedImagesPane;
    @FXML
    protected CheckBox shadowCheck, arcCheck, scoreCheck;
    @FXML
    protected ComboBox<String> chessSizeSelector;
    @FXML
    protected TableView<ScoreRuler> rulersTable;
    @FXML
    protected TableColumn<ScoreRuler, Integer> numberColumn, scoreColumn;
    @FXML
    protected RadioButton guaiRadio, benRadio, guaiBenRadio, muteRadio, customizedSoundRadio,
            deadRenewRadio, deadChanceRadio, deadPromptRadio,
            speed1Radio, speed2Radio, speed3Radio, speed5Radio,
            flush0Radio, flush1Radio, flush2Radio, flush3Radio;
    @FXML
    protected HBox selectSoundBox;
    @FXML
    protected Button helpMeButton;
    @FXML
    protected ToggleButton catButton;
    @FXML
    protected ScrollPane scrollPane;
    @FXML
    protected ControlWebView imageInfoController;
    @FXML
    protected ControlColorSet colorSetController;

    public GameEliminationController() {
        baseTitle = message("GameElimniation");
        TipsLabelKey = "GameEliminationComments";
    }

    @Override
    public void initControls() {
        try {
            super.initControls();
            chessBoard = new HashMap();
            scoreRulers = new HashMap();
            scoreRecord = new HashMap();
            scoreRulersData = FXCollections.observableArrayList();
            countedChesses = new ArrayList();
            random = new Random();
            defaultStyle = "-fx-background-color: transparent; -fx-border-style: solid inside;"
                    + "-fx-border-width: 2; -fx-border-radius: 3; -fx-border-color: transparent;";
            arcStyle = "-fx-background-color: white; -fx-border-style: solid inside;"
                    + "-fx-border-width: 2; -fx-border-radius: 10;-fx-background-radius: 10; -fx-border-color: transparent;";
            shadowStyle = "-fx-background-color: white;-fx-border-style: solid inside;"
                    + "-fx-border-width: 2; -fx-border-radius: 3; -fx-border-color: white;";
            focusStyle = "-fx-border-style: solid inside;-fx-border-width: 2;"
                    + "-fx-border-radius: 3;" + "-fx-border-color: blue;";

            flushDuration = 200;
            minimumAdjacent = 3;

            colorSetController.init(this, baseName + "Color", Color.RED);
            colorSetController.setNotify.addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue<? extends Boolean> v, Boolean ov, Boolean nv) {
                    String name = "color:" + colorSetController.name();
                    addImageAddress(name);
                }
            });

            catButton.selectedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue ov, Boolean oldVal, Boolean newVal) {
                    if (firstClick != null) {
                        recoverStyle(firstClick.getX(), firstClick.getY());
                        firstClick = null;
                    }
                    autoPlaying = catButton.isSelected();
                    if (autoPlaying) {
                        autoLabel.setText(message("Autoplaying"));
                        findAdjacentAndEliminate();
                    } else {
                        autoLabel.setText("");
                    }
                }
            });

            imagesListview.setCellFactory((ListView<ImageItem> param) -> {
                ListImageItemCell cell = new ListImageItemCell();
                return cell;
            });

            imagesListview.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
            imagesListview.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<ImageItem>() {
                @Override
                public void changed(ObservableValue ov, ImageItem oldVal, ImageItem newVal) {
                    viewImage();
                }
            });
            imagesListview.getItems().addListener(new ListChangeListener<ImageItem>() {
                @Override
                public void onChanged(ListChangeListener.Change<? extends ImageItem> c) {
                    chessesLabel.setText(message("Count") + ": " + imagesListview.getItems().size());
                }
            });

            chessWidth = 50;
            chessSizeSelector.getItems().addAll(Arrays.asList(
                    "50", "40", "60", "30", "80"
            ));

            shadowCheck.setSelected(UserConfig.getBoolean("GameEliminationShadow", false));
            arcCheck.setSelected(UserConfig.getBoolean("GameEliminationArc", false));
            chessSizeSelector.getSelectionModel().select(UserConfig.getString("GameEliminationChessImageSize", "50"));

            imageInfoController.setParent(this);

        } catch (Exception e) {
            MyBoxLog.debug(e);
        }
    }

    @Override
    public void setControlsStyle() {
        try {
            super.setControlsStyle();
            NodeStyleTools.setTooltip(createButton, message("NewGame") + "\nn / Ctrl+n");
            NodeStyleTools.setTooltip(helpMeButton, message("HelpMeFindExchange") + "\nh / Ctrl+h");
            NodeStyleTools.setTooltip(catButton, message("AutoPlayGame") + "\np / Ctrl+p");
        } catch (Exception e) {
            MyBoxLog.debug(e);
        }
    }

    @Override
    public boolean afterSceneLoaded() {
        if (!super.afterSceneLoaded()) {
            return false;
        }
        loadChesses(true);
        return true;
    }

    public void loadChesses(boolean resetGame) {
        if (task != null) {
            task.cancel();
        }
        task = new FxSingletonTask<Void>(this) {
            private List<ImageItem> items;

            @Override
            protected boolean handle() {
                try {
                    items = new ArrayList<>();
                    List<ImageItem> predefinedItems = InternalImages.allWithColors();
                    List<String> addresses = null;
                    String savedNames = UserConfig.getString("GameEliminationChessImages", null);
                    if (savedNames != null) {
                        addresses = Arrays.asList(savedNames.split(","));
                    }
                    if (addresses != null) {
                        for (String address : addresses) {
                            boolean predefined = false;
                            for (ImageItem item : predefinedItems) {
                                if (address.equals(item.getAddress())) {
                                    items.add(item);
                                    predefined = true;
                                    break;
                                }
                            }
                            if (!predefined) {
                                items.add(new ImageItem(address));
                            }
                        }
                    }
                    if (items.size() <= minimumAdjacent) {
                        for (ImageItem pitem : predefinedItems) {
                            boolean existed = false;
                            for (ImageItem eitem : items) {
                                if (eitem.getAddress().equals(pitem.getAddress())) {
                                    existed = true;
                                    break;
                                }
                            }
                            if (!existed) {
                                items.add(pitem);
                            }
                            if (items.size() > minimumAdjacent) {
                                break;
                            }
                        }
                    }
                    return true;
                } catch (Exception e) {
                    error = e.toString();
                    return false;
                }
            }

            @Override
            protected void whenSucceeded() {
                imagesListview.getItems().setAll(items);
                if (resetGame) {
                    okChessesAction();
                    initRulersTab();
                    initSettingsTab();
                }
            }

        };
        start(task);
    }

    protected void initRulersTab() {
        try {
            rulersTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
            rulersTable.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
                @Override
                public void changed(ObservableValue ov, Object t, Object t1) {
                    if (!isSettingValues) {

                    }
                }
            });

            rulersTable.setItems(scoreRulersData);

            numberColumn.setCellValueFactory(new PropertyValueFactory<>("adjacentNumber"));

            scoreColumn.setCellValueFactory(new PropertyValueFactory<>("score"));
            scoreColumn.setCellFactory(new Callback<TableColumn<ScoreRuler, Integer>, TableCell<ScoreRuler, Integer>>() {
                @Override
                public TableCell<ScoreRuler, Integer> call(TableColumn<ScoreRuler, Integer> param) {
                    TableAutoCommitCell<ScoreRuler, Integer> cell
                            = new TableAutoCommitCell<ScoreRuler, Integer>(new IntegerStringFromatConverter()) {

                        @Override
                        public boolean valid(Integer value) {
                            return value != null && value >= 0;
                        }

                        @Override
                        public boolean setCellValue(Integer value) {
                            try {
                                if (!valid(value) || !isEditingRow()) {
                                    cancelEdit();
                                    return false;
                                }
                                ScoreRuler row = scoreRulersData.get(editingRow);
                                if (row == null || value == null || value < 0) {
                                    cancelEdit();
                                    return false;
                                }
                                row.score = value;
                                return super.setCellValue(value);
                            } catch (Exception e) {
                                MyBoxLog.debug(e);
                                return false;
                            }
                        }
                    };
                    return cell;
                }
            });
            scoreColumn.getStyleClass().add("editable-column");

        } catch (Exception e) {
            MyBoxLog.debug(e);
        }
    }

    protected void initSettingsTab() {
        try {
            isSettingValues = true;

            guaiRadio.selectedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue ov, Boolean oldVal,
                        Boolean newVal) {
                    if (isSettingValues) {
                        return;
                    }
                    if (newVal) {
                        UserConfig.setString("GameEliminationSound", "Guai");
                    }
                }
            });
            benRadio.selectedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue ov, Boolean oldVal,
                        Boolean newVal) {
                    if (isSettingValues) {
                        return;
                    }
                    if (newVal) {
                        UserConfig.setString("GameEliminationSound", "Ben");
                    }
                }
            });
            guaiBenRadio.selectedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue ov, Boolean oldVal,
                        Boolean newVal) {
                    if (isSettingValues) {
                        return;
                    }
                    if (newVal) {
                        UserConfig.setString("GameEliminationSound", "GuaiBen");
                    }
                }
            });
            muteRadio.selectedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue ov, Boolean oldVal,
                        Boolean newVal) {
                    if (isSettingValues) {
                        return;
                    }
                    if (newVal) {
                        UserConfig.setString("GameEliminationSound", "Mute");
                    }
                }
            });
            customizedSoundRadio.selectedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue ov, Boolean oldVal,
                        Boolean newVal) {
                    if (isSettingValues) {
                        return;
                    }
                    if (newVal) {
                        UserConfig.setString("GameEliminationSound", "Customized");
                    }
                }
            });
            String sound = UserConfig.getString("GameEliminationSound", "Guai");
            switch (sound) {
                case "Ben":
                    benRadio.setSelected(true);
                    break;
                case "GuaiBen":
                    guaiBenRadio.setSelected(true);
                    break;
                case "Mute":
                    muteRadio.setSelected(true);
                    break;
                case "Customized":
                    customizedSoundRadio.setSelected(true);
                    break;
                default:
                    guaiRadio.setSelected(true);
                    break;
            }

            deadRenewRadio.selectedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue ov, Boolean oldVal,
                        Boolean newVal) {
                    if (isSettingValues) {
                        return;
                    }
                    UserConfig.setString("GameEliminationDead", "Renew");
                }
            });
            deadChanceRadio.selectedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue ov, Boolean oldVal,
                        Boolean newVal) {
                    if (isSettingValues) {
                        return;
                    }
                    UserConfig.setString("GameEliminationDead", "Chance");
                }
            });
            deadPromptRadio.selectedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue ov, Boolean oldVal,
                        Boolean newVal) {
                    if (isSettingValues) {
                        return;
                    }
                    UserConfig.setString("GameEliminationDead", "Prompt");
                }
            });
            String dead = UserConfig.getString("GameEliminationDead", "Renew");
            switch (dead) {
                case "Chance":
                    deadChanceRadio.setSelected(true);
                    break;
                case "Prompt":
                    deadPromptRadio.setSelected(true);
                    break;
                default:
                    deadRenewRadio.setSelected(true);
                    break;
            }

            speed1Radio.selectedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue ov, Boolean oldVal,
                        Boolean newVal) {
                    autoSpeed = 1000;
                    UserConfig.setString("GameEliminationAutoSpeed", "1");
                }
            });
            speed2Radio.selectedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue ov, Boolean oldVal,
                        Boolean newVal) {
                    autoSpeed = 2000;
                    UserConfig.setString("GameEliminationAutoSpeed", "2");
                }
            });
            speed3Radio.selectedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue ov, Boolean oldVal,
                        Boolean newVal) {
                    autoSpeed = 3000;
                    UserConfig.setString("GameEliminationAutoSpeed", "3");
                }
            });
            speed5Radio.selectedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue ov, Boolean oldVal,
                        Boolean newVal) {
                    autoSpeed = 5000;
                    UserConfig.setString("GameEliminationAutoSpeed", "5");
                }
            });
            autoSpeed = 2000;
            String speed = UserConfig.getString("GameEliminationAutoSpeed", "2");
            switch (speed) {
                case "1":
                    speed1Radio.setSelected(true);
                    break;
                case "3":
                    speed3Radio.setSelected(true);
                    break;
                case "5":
                    speed5Radio.setSelected(true);
                    break;
                default:
                    speed2Radio.setSelected(true);
                    break;
            }

            flush0Radio.selectedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue ov, Boolean oldVal,
                        Boolean newVal) {
                    flushTimes = 0;
                    eliminateDelay = flushDuration * (2 * flushTimes + 1);
                    UserConfig.setString("GameEliminationFlushTime", "0");
                }
            });
            flush1Radio.selectedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue ov, Boolean oldVal,
                        Boolean newVal) {
                    flushTimes = 1;
                    eliminateDelay = flushDuration * (2 * flushTimes + 1);
                    UserConfig.setString("GameEliminationFlushTime", "1");
                }
            });
            flush2Radio.selectedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue ov, Boolean oldVal,
                        Boolean newVal) {
                    flushTimes = 2;
                    eliminateDelay = flushDuration * (2 * flushTimes + 1);
                    UserConfig.setString("GameEliminationFlushTime", "2");
                }
            });
            flush3Radio.selectedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue ov, Boolean oldVal,
                        Boolean newVal) {
                    flushTimes = 3;
                    eliminateDelay = flushDuration * (2 * flushTimes + 1);
                    UserConfig.setString("GameEliminationFlushTime", "3");
                }
            });
            flushTimes = 2;
            eliminateDelay = flushDuration * (2 * flushTimes + 1);
            String flush = UserConfig.getString("GameEliminationFlushTime", "2");
            switch (flush) {
                case "1":
                    flush1Radio.setSelected(true);
                    break;
                case "3":
                    flush3Radio.setSelected(true);
                    break;
                case "0":
                    flush0Radio.setSelected(true);
                    break;
                default:
                    flush2Radio.setSelected(true);
                    break;
            }

            scoreCheck.selectedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue ov, Boolean oldVal,
                        Boolean newVal) {
                    UserConfig.setBoolean("GameEliminationPopScores", scoreCheck.isSelected());
                }
            });
            scoreCheck.setSelected(UserConfig.getBoolean("GameEliminationPopScores", true));

            selectSoundBox.disableProperty().bind(customizedSoundRadio.selectedProperty().not());

            String sfile = UserConfig.getString("GameEliminationSoundFile", null);
            if (sfile != null) {
                soundFile = new File(sfile);
                if (soundFile.exists()) {
                    soundFileLabel.setText(soundFile.getAbsolutePath());
                } else {
                    soundFile = null;
                }
            }

            isSettingValues = false;

        } catch (Exception e) {
            MyBoxLog.debug(e);
        }
    }

    public void viewImage() {
        if (isSettingValues) {
            return;
        }
        imageInfoController.clear();
        ImageItem selected = imagesListview.getSelectionModel().getSelectedItem();
        if (selected == null || selected.isColor()) {
            return;
        }
        File file = selected.getFile();
        if (file == null || !file.exists()) {
            return;
        }
        String body = "<Img src='" + file.toURI().toString() + "' width=" + selected.getWidth() + ">\n";
        String comments = selected.getComments();
        if (comments != null && !comments.isBlank()) {
            body += "<BR>" + message(comments);
        }
        imageInfoController.loadContent(HtmlWriteTools.html(body));
    }

    @Override
    public void selectSourceFileDo(File file) {
        if (file == null) {
            return;
        }
        recordFileOpened(file);
        addImageAddress(file.getAbsolutePath());
    }

    @FXML
    protected void okChessesAction() {
        makeChesses();
    }

    @FXML
    protected void okRulersAction() {
        if (isSettingValues) {
            return;
        }
        catButton.setSelected(false);
        try {
            countedChesses.clear();
            String s = "";
            for (Node node : countedImagesPane.getChildren()) {
                CheckBox cbox = (CheckBox) node;
                if (cbox.isSelected()) {
                    int index = (int) cbox.getUserData();
                    countedChesses.add(index);
                    ImageItem item = getImageItem(index);
                    if (s.isBlank()) {
                        s += item.getAddress();
                    } else {
                        s += "," + item.getAddress();
                    }
                }
            }
            if (countedChesses.isEmpty()) {
                if (!PopTools.askSure(getTitle(), message("SureNoScore"))) {
                    return;
                }
            }
            UserConfig.setString("GameEliminationCountedImages", s);

            scoreRulers.clear();
            s = "";
            for (int i = 0; i < scoreRulersData.size(); ++i) {
                ScoreRuler r = scoreRulersData.get(i);
                scoreRulers.put(r.adjacentNumber, r.score);
                if (!s.isEmpty()) {
                    s += ",";
                }
                s += r.adjacentNumber + "," + r.score;
            }
            UserConfig.setString("GameElimniationScoreRulers", s);

            tabPane.getSelectionModel().select(playTab);
            newGame(true);

        } catch (Exception e) {
            MyBoxLog.debug(e);
        }
    }

    public boolean addImageAddress(String address) {
        if (isSettingValues || address == null) {
            return false;
        }
        for (ImageItem item : imagesListview.getItems()) {
            if (item.getAddress().equals(address)) {
                popError(message("AlreadyExisted"));
                return false;
            }
        }
        ImageItem item = new ImageItem().setAddress(address);
        imagesListview.getItems().add(0, item);
        imagesListview.scrollTo(0);
        return true;
    }

    @FXML
    protected void clearChessesAction() {
        imagesListview.getItems().clear();
    }

    @FXML
    protected void deleteChessesAction() {
        List<ImageItem> selected = imagesListview.getSelectionModel().getSelectedItems();
        if (selected == null || selected.isEmpty()) {
            popError(message("SelectToHandle"));
            return;
        }
        imagesListview.getItems().removeAll(selected);
    }

    @FXML
    protected void recoverChessesAction() {
        loadChesses(false);
    }

    @FXML
    protected void chessExamples() {
        ImageExampleSelectController controller = ImageExampleSelectController.open(this, true);
        controller.notify.addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue ov, Boolean oldValue, Boolean newValue) {
                List<ImageItem> items = controller.selectedItems();
                if (items == null || items.isEmpty()) {
                    controller.popError(message("SelectToHandle"));
                    return;
                }
                controller.close();
                for (ImageItem item : items) {
                    String address = item.getAddress();
                    for (ImageItem litem : imagesListview.getItems()) {
                        if (litem.getAddress().equals(address)) {
                            address = null;
                            break;
                        }
                    }
                    if (address != null) {
                        imagesListview.getItems().add(0, item);
                    }
                }
                imagesListview.scrollTo(0);
            }
        });
    }

    @FXML
    protected void clearCountedImagesAction() {
        if (isSettingValues) {
            return;
        }
        for (Node node : countedImagesPane.getChildren()) {
            CheckBox cbox = (CheckBox) node;
            cbox.setSelected(false);
        }
    }

    @FXML
    protected void allCountedImagesAction() {
        if (isSettingValues) {
            return;
        }
        for (Node node : countedImagesPane.getChildren()) {
            CheckBox cbox = (CheckBox) node;
            cbox.setSelected(true);
        }
    }

    @FXML
    @Override
    public void createAction() {
        if (isSettingValues) {
            return;
        }
        catButton.setSelected(false);
        newGame(true);
    }

    @FXML
    protected void settingsAction() {
        tabPane.getSelectionModel().select(settingsTab);
    }

    @FXML
    public void helpMeAction() {
        if (isSettingValues || autoPlaying) {
            return;
        }
        Adjacent adjacent = findValidExchange();
        if (adjacent != null) {
            flush(adjacent.exchangei1, adjacent.exchangej1);
            flush(adjacent.exchangei2, adjacent.exchangej2);
        } else {
            promptDeadlock();
        }
    }

    protected void setAutoplay() {
        catButton.setSelected(!catButton.isSelected());
    }

    @FXML
    public void selectSoundFile() {
        try {
            if (!checkBeforeNextAction()) {
                return;
            }
            File file = mara.mybox.fxml.FxFileTools.selectFile(this, UserConfig.getPath("MusicPath"),
                    FileFilters.Mp3WavExtensionFilter);
            if (file == null) {
                return;
            }
            selectSoundFile(file);
        } catch (Exception e) {
//            MyBoxLog.error(e);
        }
    }

    public void selectSoundFile(File file) {
        recordFileOpened(file);
        String suffix = FileNameTools.ext(file.getName());
        if (suffix == null
                || (!"mp3".equals(suffix.toLowerCase()) && !"wav".equals(suffix.toLowerCase()))) {
            alertError(message("OnlySupportMp3Wav"));
            return;
        }
        soundFile = file;
        UserConfig.setString("MusicPath", file.getParent());
        UserConfig.setString("GameEliminationSoundFile", file.getAbsolutePath());
        soundFileLabel.setText(file.getAbsolutePath());
        SoundTools.mp3(soundFile);
    }

    @FXML
    public void showSoundFileMenu(Event event) {
        if (AppVariables.fileRecentNumber <= 0) {
