package leigh.eo;

import leigh.mecha.log.MechaLogger;
import leigh.mecha.log.MechaLoggerFactory;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.WeakHashMap;

/**
 * Implementation of {@link EditTracker} which provides notification to listeners when values in an EO change.
 * The references to the EOs are weak, allowing them to be garbage collected.
 * <p>
 * This class makes no attempt at event suppression, and does not correlate the generation of an event with any
 * particular listener. Therefore, a software agent A creating a change to an EO should expect to receive the
 * update for it. The software agent A is responsible for understanding whether this is a "duplicate" update,
 * typically by comparing its internal state to the update provided by the event, and determining if processing
 * needs to be occur.
 *
 * @author C. Alexaxnder Leigh
 * @since mk14 mod10 (CRYSTAL)
 */
public class EditEventGenerator implements EditTracker {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(EditEventGenerator.class);
    private final WeakHashMap<EO, ArrayList<EOListener>> listeners = new WeakHashMap<>();
    private final WeakHashMap<EOLoop, ArrayList<EOLoopListener>> loopListeners = new WeakHashMap<>();

    @Override
    public void dirty(EO eo, String key, Serializable oldValue, Serializable newValue, EOListener parent) {
        logger.debug("Value has mutated. [eo: {}] [key: {}] [old: {}] [new: {}]", eo, key, oldValue, newValue);
        // We only want to fire the change event listeners if the value is actually changing.

        ArrayList<EOListener> l = listeners.get(eo);

        if (l == null) {
            logger.debug("No listeners for this EO.");
            return;
        }

        logger.debug("Listeners found: {}", l);

        for (EOListener listener : l) {
            if (l.equals(parent)) continue;
            try {
                listener.valueUpdated(eo, key, oldValue, newValue);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void objRemoved(EOLoop loop, EO eo, int index) {
        if (loopListeners.containsKey(loop))
            for (EOLoopListener listener : loopListeners.get(loop)) {
                logger.debug("Calling removal event handler: {} {} {} {}", listener, loop, eo, index);
                listener.removed(loop, eo, index);
            }
    }

    @Override
    public void objAdded(EOLoop loop, EO eo) {
        logger.debug("objAdded() called. Listeners: {} {}", loopListeners.containsKey(loop), loopListeners);

        if (loopListeners.containsKey(loop))
            for (EOLoopListener listener : loopListeners.get(loop)) {
                listener.added(loop, eo);
            }
    }

    public void addLoopListener(EOLoop loop, EOLoopListener listener) {
        logger.debug("addLoopListener() called.");
        ArrayList<EOLoopListener> l = loopListeners.get(loop);
        if (l == null) {
            l = new ArrayList<>();
            loopListeners.put(loop, l);
        }
        l.add(listener);
    }

    /**
     * Add a listener for the given object. Note that if the value stored in the key is a loop, then this listener
     * WILL NOT fire. Instead you must register an event handler in this case with <code>addLoopListener()</code>.
     */
    public void addListener(EO eo, EOListener listener) {
        logger.debug("addListener() called.");
        ArrayList<EOListener> l = listeners.get(eo);
        if (l == null) {
            l = new ArrayList<>();
            listeners.put(eo, l);
        }
        l.add(listener);
    }
}
