package mara.mybox.controller;

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.ArrayList;
import java.util.List;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.input.MouseEvent;
import javafx.scene.text.Text;
import javafx.stage.Modality;
import mara.mybox.data.GeographyCode;
import mara.mybox.data.GeographyCodeLevel;
import static mara.mybox.db.DerbyBase.dbHome;
import static mara.mybox.db.DerbyBase.login;
import static mara.mybox.db.DerbyBase.protocol;
import mara.mybox.db.TableGeographyCode;
import mara.mybox.fxml.FxmlControl;
import mara.mybox.value.AppVariables;
import static mara.mybox.value.AppVariables.logger;
import static mara.mybox.value.AppVariables.message;

/**
 * @Author Mara
 * @CreateDate 2020-04-23
 * @License Apache License Version 2.0
 */
public class GeographyCodeSelectorController extends BaseController {

    protected GeographyCodeUserController userController;
    protected LoadingController loading;

    @FXML
    protected TreeView<Text> treeView;
    @FXML
    protected CheckBox leafCheck;

    public GeographyCodeSelectorController() {
        baseTitle = message("GeographyCode");
    }

    @Override
    public void initializeNext() {
        try {
            super.initializeNext();

            FxmlControl.setTooltip(leafCheck, message("CheckLeafNodesComments"));
            leafCheck.setSelected(AppVariables.getUserConfigBoolean("GeographyCodesTreeCheckLeafNodes", true));
            leafCheck.selectedProperty().addListener(
                    (ObservableValue<? extends Boolean> ov, Boolean oldValue, Boolean newValue) -> {
                        AppVariables.setUserConfigValue("GeographyCodesTreeCheckLeafNodes", newValue);
                    });

        } catch (Exception e) {
            logger.error(e.toString());
        }
    }

    public void loadTree(GeographyCodeUserController userController) {
        this.userController = userController;
        loadTree();
    }

    protected void loadTree() {
        treeView.setRoot(null);
        synchronized (this) {
            if (task != null) {
                return;
            }
            task = new SingletonTask<Void>() {
                private GeographyCode earch;
                private List<GeographyCode> continents, others;
                private List<Long> haveChildren;

                @Override
                protected boolean handle() {
                    try ( Connection conn = DriverManager.getConnection(protocol + dbHome() + login)) {
                        String sql = "SELECT * FROM Geography_Code WHERE gcid=1 ";
                        if (loading != null) {
                            loading.setInfo(sql);
                        }
                        earch = TableGeographyCode.earth(conn);
                        if (earch == null) {
                            GeographyCode.predefined(conn);
                            earch = TableGeographyCode.earth(conn);
                            if (earch == null) {
                                return false;
                            }
                        }
                        conn.setReadOnly(true);
                        if (loading != null) {
                            loading.setInfo(message("LoadingContinents"));
                        }
                        sql = "SELECT * FROM Geography_Code WHERE level=2 AND continent>=2 AND continent<=8 ORDER BY gcid ";
                        if (loading != null) {
                            loading.setInfo(sql);
                        }
                        continents = TableGeographyCode.queryCodes(conn, sql, true);

                        String condition = "";
                        for (int i = 3; i <= 9; i++) {
                            String ic = "level=" + i;
                            for (int j = 3; j < i; j++) {
                                GeographyCodeLevel jLevel = new GeographyCodeLevel(j);
                                ic += " AND " + jLevel.getKey() + "<=0 ";
                            }
                            if (condition.isBlank()) {
                                condition += "( " + ic + ")";
                            } else {
                                condition += " OR ( " + ic + ")";
                            }
                        }
                        sql = "SELECT * FROM Geography_Code WHERE "
                                + " ( " + condition + " ) AND "
                                + " (continent<2 OR continent>8) "
                                + " ORDER BY gcid ";
                        others = TableGeographyCode.queryCodes(conn, sql, true);

                        if (leafCheck.isSelected()) {
                            if (loading != null) {
                                loading.setInfo(message("CheckingLeafNodes"));
                            }
                            haveChildren = new ArrayList();
                            List<GeographyCode> checkEmpty = new ArrayList<>();
                            checkEmpty.addAll(continents);
                            if (others != null && !others.isEmpty()) {
                                checkEmpty.addAll(others);
                            }
                            haveChildren = TableGeographyCode.haveChildren(conn, checkEmpty);
                        } else {
                            haveChildren = null;
                        }

                        return true;
                    } catch (Exception e) {
                        error = e.toString();
                        return false;
                    }
                }

                @Override
                protected void whenSucceeded() {
                    Text earthNode = new Text(message("Earth"));
                    earthNode.setOnMouseClicked((MouseEvent event) -> {
                        userController.codeSelected(earch);
                    });
                    TreeItem<Text> earthItem = new TreeItem(earthNode);
                    earthItem.setExpanded(true);
                    treeView.setRoot(earthItem);
                    treeView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
                    addNodes(earthItem, continents, haveChildren);

                    if (!others.isEmpty()) {
                        Text othersNode = new Text(message("Others"));
                        TreeItem<Text> othersItem = new TreeItem(othersNode);
                        earthItem.getChildren().add(othersItem);
                        addNodes(othersItem, others, haveChildren);
                    }
                }
            };
            if (userController != null) {
                loading = userController.openHandlingStage(task, Modality.WINDOW_MODAL);
            } else {
                loading = openHandlingStage(task, Modality.WINDOW_MODAL);
            }
            Thread thread = new Thread(task);
            thread.setDaemon(true);
            thread.start();
        }
    }

    protected void addNodes(TreeItem<Text> parent, List<GeographyCode> codes,
            List<Long> haveChildren) {
        if (parent == null || codes == null || codes.isEmpty()) {
            return;
        }
        for (GeographyCode code : codes) {
            long codeid = code.getGcid();
            Text codeNode = new Text(code.getName());
            codeNode.setOnMouseClicked((MouseEvent event) -> {
                userController.codeSelected(code);
            });
            codeNode.setUserData(codeid);
            TreeItem<Text> codeItem = new TreeItem(codeNode);
            parent.getChildren().add(codeItem);

            if (!leafCheck.isSelected()
                    || (haveChildren != null && haveChildren.contains(codeid))) {
                TreeItem<Text> dummyItem = new TreeItem(new Text("Loading"));
                codeItem.getChildren().add(dummyItem);
                codeItem.setExpanded(false);
                codeItem.expandedProperty().addListener(
                        (ObservableValue<? extends Boolean> ov, Boolean oldVal, Boolean newVal) -> {
                            if (newVal && !codeItem.isLeaf() && !loaded(codeItem)) {
                                nodeTree(codeItem, code);
                            }
                        });
            }
        }
    }

    protected boolean loaded(TreeItem<Text> item) {
        if (item == null || item.isLeaf()) {
            return true;
        }
        try {
            TreeItem<Text> dummyItem = (TreeItem<Text>) (item.getChildren().get(0));
            return !"Loading".equals(dummyItem.getValue().getText());
        } catch (Exception e) {
            return true;
        }
    }

    protected void nodeTree(TreeItem<Text> parent, GeographyCode code) {
        if (parent == null || code == null) {
            return;
        }
        parent.getChildren().clear();
        synchronized (this) {
            if (task != null) {
                return;
            }
            task = new SingletonTask<Void>() {
                private List<GeographyCode> children;
                private List<Long> haveChildren;

                @Override
                protected boolean handle() {
                    try ( Connection conn = DriverManager.getConnection(protocol + dbHome() + login)) {
                        conn.setReadOnly(true);
                        GeographyCodeLevel level = code.getLevelCode();
                        int codeLevel = level.getLevel();
                        if (level.getKey() == null || codeLevel < 2 || codeLevel > 8) {
                            return false;
                        }
                        if (loading != null) {
                            loading.setInfo(message("Loading") + " " + level.getName()
                                    + " " + code.getName());
                        }
                        String condition = "";
                        for (int i = codeLevel + 1; i <= 9; i++) {
                            String ic = "level=" + i;
                            for (int j = codeLevel + 1; j < i; j++) {
                                GeographyCodeLevel jLevel = new GeographyCodeLevel(j);
                                ic += " AND " + jLevel.getKey() + "<=0 ";
                            }
                            if (condition.isBlank()) {
                                condition += "( " + ic + ")";
                            } else {
                                condition += " OR ( " + ic + ")";
                            }
                        }
                        String sql = "SELECT * FROM Geography_Code WHERE "
                                + " ( " + condition + " ) AND "
                                + level.getKey() + "=" + code.getGcid()
                                + " ORDER BY gcid ";
                        children = TableGeographyCode.queryCodes(conn, sql, true);
                        if (children == null || children.isEmpty()) {
                            return true;
                        }
                        if (leafCheck.isSelected()) {
                            if (loading != null) {
                                loading.setInfo(message("CheckingLeafNodes"));
                            }
                            haveChildren = TableGeographyCode.haveChildren(conn, children);
                        } else {
                            haveChildren = null;
                        }
                        return true;
                    } catch (Exception e) {
                        error = e.toString();
                        return false;
                    }
                }

                @Override
                protected void whenSucceeded() {
                    addNodes(parent, children, haveChildren);
                }
            };
            if (userController != null) {
                loading = userController.openHandlingStage(task, Modality.WINDOW_MODAL);
            } else {
                loading = openHandlingStage(task, Modality.WINDOW_MODAL);
            }
            Thread thread = new Thread(task);
            thread.setDaemon(true);
            thread.start();
        }
    }

    protected void addNode(GeographyCode parent, GeographyCode child) {
        if (parent == null || child == null) {
            return;
        }
        addNode(treeView.getRoot(), parent, child);
    }

    protected void addNode(TreeItem<Text> node, GeographyCode parent, GeographyCode child) {
        if (node == null || parent == null || child == null) {
            return;
        }
        if (node.getValue().getUserData() != null) {
            long current = (long) (node.getValue().getUserData());
            if (current == parent.getGcid()) {
                Text childNode = new Text(child.getName());
                childNode.setOnMouseClicked((MouseEvent event) -> {
                    userController.codeSelected(child);
                });
                childNode.setUserData(child.getGcid());
                TreeItem<Text> codeItem = new TreeItem(childNode);
                node.getChildren().add(codeItem);
                node.setExpanded(true);
                return;
            }
        }
        if (node.isLeaf()) {
            return;
        }
        for (TreeItem<Text> subNode : node.getChildren()) {
            addNode(subNode, parent, child);
        }
    }

    protected void removeNode(GeographyCode code) {
        if (code == null) {
            return;
        }
        removeNode(treeView.getRoot(), code);
    }

    protected void removeNode(TreeItem<Text> node, GeographyCode code) {
        if (node == null || code == null || node.isLeaf()) {
            return;
        }
        for (TreeItem<Text> subNode : node.getChildren()) {
            if (subNode.getValue().getUserData() != null) {
                long subCode = (long) (subNode.getValue().getUserData());
                if (subCode == code.getGcid()) {
                    node.getChildren().remove(subNode);
                    return;
                }
            }
            removeNode(subNode, code);
        }
    }

}