////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////

package pp.view;

import com.jme3.scene.Node;
import com.jme3.scene.Spatial;

import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.HashMap;
import java.util.Map;

/**
 * Abstract base class for keeping the scene graph (=view) in sync with the model.
 */
public abstract class ModelViewSynchronizer<I> {
    private static final Logger LOGGER = System.getLogger(ModelViewSynchronizer.class.getName());
    private final Node itemNode = new Node("items"); //NON-NLS
    private final Map<I, Spatial> itemMap = new HashMap<>();

    /**
     * Saves the game state and the node.
     *
     * @param root particular node
     */
    protected ModelViewSynchronizer(Node root) {
        root.attachChild(itemNode);
    }

    /**
     * Returns the spatial representing the specified item,
     * or null if the item has no view counterpart.
     */
    public Spatial getSpatial(I item) {
        return itemMap.get(item);
    }

    /**
     * removes spatial from map
     *
     * @param item spatial that should be removed
     */
    public void delete(I item) {
        final Spatial spatial = itemMap.remove(item);
        if (spatial != null) {
            spatial.removeFromParent();
            LOGGER.log(Level.DEBUG, "removed spatial for {0} in {1}", item, this); //NON-NLS
        }
    }

    /**
     * add spatial to map
     *
     * @param item spatial that schuld be added
     */
    public void add(I item) {
        if (itemMap.containsKey(item)) {
            LOGGER.log(Level.WARNING, "Item {0} already managed by {1}", item, this); //NON-NLS
            return;
        }
        final Spatial spatial = translate(item);
        itemMap.put(item, spatial);
        LOGGER.log(Level.DEBUG, "added spatial for {0} in {1}", item, this); //NON-NLS
        if (spatial != null)
            itemNode.attachChild(spatial);
    }

    /**
     * Removed every item
     */
    public void clear() {
        LOGGER.log(Level.DEBUG, "clear"); //NON-NLS
        itemMap.clear();
        itemNode.detachAllChildren();
    }

    /**
     * Creates the spatial for the specified item. Implementations may decide to return null. This
     * means that the item shall not be represented in the scene graph.
     *
     * @param item the item whose representing spatial is asked for
     * @return the spatial of the item, or null if the item shall not be represented by a spatial.
     */
    protected abstract Spatial translate(I item);
}
