/*
 *
 *  Copyright (C) 2021-2024 Andrew Gegg
 *
 * 	This file is part of the Gardeners Notebook application
 *
 *  The Gardeners Notebook application is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/gpl.html>.
 *
 *
 */

/*
	Change log
	3.0.0	First version
    3.0.4	Use new convenience class NotebookBeanDeleter for deletion.
    		Set first row of catalogue selected.
    3.2.1   Support Lifecycle analysis
 */

package uk.co.gardennotebook;

import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxTreeTableCell;
import javafx.scene.control.cell.TextFieldTreeTableCell;
import javafx.scene.layout.AnchorPane;
import javafx.scene.text.Text;
import javafx.stage.WindowEvent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.EntryMessage;
import uk.co.gardennotebook.fxbean.LocationBean;
import uk.co.gardennotebook.spi.GNDBException;

import java.io.IOException;
//import java.util.Optional;
import java.util.ResourceBundle;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

/**
 *	Controller class for create/update of Location catalogue
 *
 *	@author Andy Gegg
 *	@version	3.2.1
 *	@since	3.0.0
 */
public class LocationCat extends AnchorPane implements INotebookLoadable
{
    private static final Logger LOGGER = LogManager.getLogger();

    @FXML
    private ResourceBundle resources;

    @FXML
    private TreeTableView<LocationBean> tblCatalogue;
    @FXML
    private TreeTableColumn<LocationBean, String> colName;
    @FXML
    private TreeTableColumn<LocationBean, String> colDescription;
    @FXML
    private TreeTableColumn<LocationBean, Boolean> colUnderCover;
    @FXML
    private TreeTableColumn<LocationBean, LocationBean> colComment;

    @FXML
    private Button btnChange;
    @FXML
    private Button btnDelete;

    @FXML
    private MenuItem ctxmnuDelete;


    private Consumer<Node> loadSplit;
    private Consumer<Node> clearSplit;
    private BiConsumer<String, Node> loadTab;
    private Consumer<Node> clearTab;

    LocationCat()
    {
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/LocationCat.fxml"),
                ResourceBundle.getBundle("notebook"));
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);
        try
        {
            fxmlLoader.load();    // NB initialize is called from in here
        } catch (IOException exception)
        {
            throw new RuntimeException(exception);
        }
    }// constructor

    /*
     * Initializes the controller class.
     */
    @FXML
    private void initialize()
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("initialize()");
        tblCatalogue.setColumnResizePolicy(NotebookResizer.using(tblCatalogue));

        colName.setCellValueFactory(cdf -> cdf.getValue().getValue().nameProperty());

        colDescription.setCellValueFactory(cdf -> cdf.getValue().getValue().descriptionProperty());
        colDescription.setCellFactory(TextFieldTreeTableCell.forTreeTableColumn());

        colUnderCover.setCellFactory(CheckBoxTreeTableCell.forTreeTableColumn(colUnderCover));
        colUnderCover.setCellValueFactory(cdf -> cdf.getValue().getValue().underCoverProperty());

        colComment.setCellValueFactory((e)-> e.getValue().valueProperty());
        colComment.setCellFactory(_ -> new EditorCommentTreeTableCell<>(resources));

        loadLocationTree();

        // only allow change and delete if there is a row selected
        btnChange.disableProperty().bind(tblCatalogue.getSelectionModel().selectedItemProperty().isNull());
        btnDelete.setDisable(true);
        btnDelete.setDisable(true);
        tblCatalogue.getSelectionModel().selectedItemProperty().addListener((_, _, nval) ->{
            try
            {
                btnDelete.setDisable( (nval == null) || !(nval.getValue().canDelete()) );
            } catch (GNDBException ex) {
                PanicHandler.panic(ex);
            }
        });

        //  Set the name column width wide enough to show the names
        int ix = 0;
        double nameWidth = new Text(colName.getText()).getLayoutBounds().getWidth();
        TreeItem<LocationBean> node;
        do
        {
            node = tblCatalogue.getTreeItem(ix);
            if (node == null) break;
            int level = tblCatalogue.getTreeItemLevel(node);
            if (node == tblCatalogue.getRoot()) continue;
            final Text t = new Text(node.
                    getValue().
                    nameProperty().
                    getValue() +
                    "  ".repeat(level));
            final double wid = t.getLayoutBounds().getWidth();
            if (wid > nameWidth) nameWidth = wid;
            ix++;
        }
        while (true);
        colName.setPrefWidth(nameWidth+30);

        tblCatalogue.getSelectionModel().selectFirst();
        Platform.runLater(() -> tblCatalogue.requestFocus());	//	this enables keyboard navigation with the up/down arrows

        LOGGER.traceExit(log4jEntryMsg);
    }	//	initialize()

    private void loadLocationTree()
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("loadTree()");

        TreeItem<LocationBean> root = new TreeItem<>();
        ObservableList<LocationBean> locs = FXCollections.emptyObservableList();

        double nameWidth = new Text(colName.getText()).getLayoutBounds().getWidth();

        // get the top level Location(s)
        try {
            locs = LocationBean.fetchTop();
        } catch (GNDBException ex) {
            PanicHandler.panic(ex);
        }

        nameWidth = addBranch(root, locs, nameWidth);
        colName.setPrefWidth(nameWidth+35);
        tblCatalogue.setRoot(root);
        for (var node : root.getChildren())
        {
            node.setExpanded(true);
        }
    }

    private  double addBranch(TreeItem<LocationBean> root, ObservableList<LocationBean> locs, double nameWidth)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("addBranch(): locs: {}", locs);

        for (var loc : locs)
        {
            final Text t = new Text(loc.getName());
            final double wid = t.getLayoutBounds().getWidth();
            if (wid > nameWidth)
                nameWidth = wid;
            TreeItem<LocationBean> branch = new TreeItem<>(loc);
            root.getChildren().add(branch);
            try {
                nameWidth = addBranch(branch, loc.getSubLocations(), nameWidth);
            } catch (GNDBException ex) {
                PanicHandler.panic(ex);
            }
        }
        return nameWidth;
    }

    @Override
    public void setLoadSplit(Consumer<Node> code)
    {
        loadSplit = code;
    }

    @Override
    public void setClearSplit(Consumer<Node> code)
    {
        clearSplit = code;
    }

    @Override
    public void setLoadTab(BiConsumer<String, Node> code)
    {
        loadTab = code;
    }

    @Override
    public void setClearTab(Consumer<Node> code)
    {
        clearTab = code;
    }

    @FXML
    private void ctxmnuOnShowing(WindowEvent event)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuOnShowing()");
        LocationBean ixBean = tblCatalogue.getSelectionModel().selectedItemProperty().get().getValue();
        try
        {
            ctxmnuDelete.setDisable( (ixBean == null) || !(ixBean.canDelete()) );
        } catch (GNDBException ex) {
            PanicHandler.panic(ex);
        }
        LOGGER.traceExit(log4jEntryMsg);
    }	//	ctxmnuOnShowing()

    @FXML
    private void btnAddOnAction(ActionEvent event)
    {
        ctxmnuAddOnAction(event);
    }

    @FXML
    private void btnChangeOnAction(ActionEvent event)
    {
        ctxmnuChangeOnAction(event);
    }

    @FXML
    private void btnDeleteOnAction(ActionEvent event)
    {
        ctxmnuDeleteOnAction(event);
    }

    @FXML
    private void ctxmnuAddOnAction(ActionEvent event)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuAddOnAction()");
        final TreeItem<LocationBean> treeBean = tblCatalogue.getSelectionModel().getSelectedItem();
        final LocationBean ixBean = (treeBean == null) ? null : treeBean.getValue();
        LocationEditor tabCon = new LocationEditor(null, ixBean);
        loadTab.accept(resources.getString("tab.location"), tabCon);

        //  handle display of new Location
        tabCon.newBeanProperty().addListener((_, _, newVal) -> {
            var newItem = new TreeItem<>(newVal);
            // find the parent
            if (newVal.hasParent())
            {
                if (newVal.getParentLocation().sameAs(ixBean))
                {
                    treeBean.getChildren().add(newItem);
                }
                else
                {   // find the parent
                    LocationBean newParent = newVal.getParentLocation();
                    int ix = 0;
                    TreeItem<LocationBean> node;
                    do
                    {
                        node = tblCatalogue.getTreeItem(ix);
                        if (node == null) break;
                        if (node == tblCatalogue.getRoot()) continue;
                        if (node.getValue().sameAs(newParent))
                        {
                            node.getChildren().add(newItem);
                            node.setExpanded(true);
                            break;
                        }
                        ix++;
                    }
                    while (true);
//                    while (node != null);
                    if (node == null)
                    {// parent node also new
                        var newPar = new TreeItem<>(newVal.getParentLocation());
                        tblCatalogue.getRoot().getChildren().add(newPar);
                        newPar.getChildren().add(newItem);
                    }
                }
            }
            else
            {   // new top-level location
                tblCatalogue.getRoot().getChildren().add(newItem);
            }
            tblCatalogue.sort();
            tblCatalogue.scrollTo(tblCatalogue.getRow(newItem));
            tblCatalogue.getSelectionModel().select(newItem);
        });
        LOGGER.traceExit(log4jEntryMsg);
    }	//	ctxmnuAddOnAction()

    @FXML
    private void ctxmnuChangeOnAction(ActionEvent event)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuChangeOnAction()");
        final TreeItem<LocationBean> treeBean = tblCatalogue.getSelectionModel().getSelectedItem(); // node holding the Location to change
        if (treeBean == null) return;

        final LocationBean ixBean = treeBean.getValue();    //  the Location to change
        if (ixBean == null)
        {
            LOGGER.debug("thisValueBean is null");
            LOGGER.traceExit(log4jEntryMsg);
            return;
        }
        final TreeItem<LocationBean> oldParentNode = treeBean.getParent();  //  parent node of the changed Location, possibly the root node
        final LocationBean oldParent = ixBean.getParentLocation();  //  parent Location of the changed Location, possibly null

        LocationEditor tabCon = new LocationEditor(ixBean);
        loadTab.accept(resources.getString("tab.location"), tabCon);

        // handle changes to the parentLocationId - redisplay correctly
        tabCon.newBeanProperty().addListener((_, _, newVal) -> {
            if (!newVal.hasParent() && oldParent == null) return;   //  top-level, not moved
            if (!newVal.hasParent())
            {   //  moved to top-level
                LOGGER.debug("newBeanProperty: listener: newVal has no parent");
                oldParentNode.getChildren().remove(treeBean);
                tblCatalogue.getRoot().getChildren().add(treeBean); //  moves whole sub-tree
                return;
            }
            if (newVal.hasParent() && newVal.getParentLocation().sameAs(oldParent)) return; //  not moved
            if (oldParent != null)
            {
                oldParentNode.getChildren().remove(treeBean);
            }
            else
            {
                tblCatalogue.getRoot().getChildren().remove(treeBean);  // it used to be top-level
            }
            LocationBean newParent = newVal.getParentLocation();
            int ix = 0;
            TreeItem<LocationBean> node;
            do
            {
                node = tblCatalogue.getTreeItem(ix);
                if (node == null) break;
                if (node == tblCatalogue.getRoot()) continue;
                if (node.getValue().sameAs(newParent))
                {
                    node.getChildren().add(treeBean);
                    node.setExpanded(true);
                    break;
                }
                ix++;
            }
            while (true);
//            while (node != null);
            if (node == null)
            {// parent node also new
                var newPar = new TreeItem<>(newParent);
                tblCatalogue.getRoot().getChildren().add(newPar);
                newPar.getChildren().add(treeBean);
            }
        });

        //  handle redisplay after delete
        tabCon.deletedBeanProperty().addListener((_, _, _) -> {
            treeBean.getParent().getChildren().remove(treeBean);
            tblCatalogue.sort();
        });
        LOGGER.traceExit(log4jEntryMsg);
    }	//	ctxmnuChangeOnAction()

    @FXML
    private void ctxmnuDeleteOnAction(ActionEvent event)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuDeleteOnAction()");
        final TreeItem<LocationBean> treeBean = tblCatalogue.getSelectionModel().getSelectedItem();
        final LocationBean ixBean = treeBean.getValue();
        if (ixBean == null)
        {
            LOGGER.debug("thisValueBean is null");
            LOGGER.traceExit(log4jEntryMsg);
            return;
        }

        NotebookBeanDeleter<LocationBean> deleterImpl = new NotebookBeanDeleter<>(resources);
        if (deleterImpl.deleteItemImpl(ixBean))
        {
//            tblCatalogue.getItems().remove(ixBean);
            treeBean.getParent().getChildren().remove(treeBean);        }

        LOGGER.traceExit(log4jEntryMsg);
    }	//	ctxmnuDeleteOnAction()

    @FXML
    private void ctxmnuAnalysisOnAction(ActionEvent event)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuAnalysisOnAction()");
        final TreeItem<LocationBean> treeBean = tblCatalogue.getSelectionModel().getSelectedItem();
        final LocationBean ixBean = treeBean.getValue();
        if (ixBean == null)
        {
            LOGGER.debug("no item selected in TreeTable");
            return;
        }

        LifecycleAnalysisTab tabCon = new LifecycleAnalysisTab(null, null, ixBean);
        loadTab.accept(resources.getString("tab.analysis.lifecycle"), tabCon);
    }

}
