/*
 * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
 *
 * Copyright (c) JCThePants (www.jcwhatever.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.jcwhatever.nucleus.utils.observer.update;

import com.jcwhatever.nucleus.collections.observer.agent.AgentHashMap;
import com.jcwhatever.nucleus.collections.observer.agent.AgentMap;
import com.jcwhatever.nucleus.utils.PreCon;

import java.util.ArrayList;
import java.util.List;

/**
 * Used for types that need to keep multiple update agents for multiple
 * update contexts.
 *
 * <p>The {@code UpdateAgentTracker} uses a lazy initialization approach to
 * prevent using resources if they are not needed. Calling the {@link #getAgent}
 * method always returns a result. If the agent is not present, a new one is created.</p>
 *
 * <p>Use {@link #hasAgent} to determine if the agent has been created yet.</p>
 *
 */
public class NamedUpdateAgents {

    volatile AgentMap<String, UpdateAgent<?>> _agents;
    private volatile String _recentName;
    private volatile UpdateAgent<?> _recent;

    /**
     * Get the number of instantiated agents.
     */
    public int size() {
        if (_agents == null)
            return 0;

        return _agents.size();
    }

    /**
     * Determine if there are any initialized agents.
     */
    public boolean isEmpty() {
        return size() == 0;
    }

    /**
     * Determine if the tracker contains an agent
     * by a certain name.
     *
     * @param name The name of the agent.
     */
    public boolean hasAgent(String name) {
        PreCon.notNull(name);

        if (_agents == null)
            return false;

        if (name.equals(_recentName)) {

            if (_recent.isDisposed()) {
                _recentName = null;
                _recent = null;
            }
            else {
                return true;
            }
        }

        UpdateAgent<?> agent = _agents.get(name);
        if (agent != null) {
            _recentName = name;
            _recent = agent;
        }

        return agent != null;
    }


    /**
     * Get an existing agent or create a new one.
     *
     * @param name  The name of the agent.
     *
     * @param <T>  The agent value type.
     */
    public <T> UpdateAgent<T> getAgent(String name) {
        PreCon.notNull(name);

        if (name.equals(_recentName)) {

            if (_recent.isDisposed()) {
                _recentName = null;
                _recent = null;
            }
            else {
                @SuppressWarnings("unchecked")
                UpdateAgent<T> recent = (UpdateAgent<T>) _recent;
                return recent;
            }
        }

        UpdateAgent<T> agent = null;

        if (_agents == null) {
            _agents = new AgentHashMap<>(5);
        }
        else {
            @SuppressWarnings("unchecked")
            UpdateAgent<T> get = (UpdateAgent<T>)_agents.get(name);
            agent = get;
        }

        if (agent == null) {
            agent = new UpdateAgent<>(3);
            _agents.set(name, agent);
        }

        return agent;
    }

    /**
     * Dispose all agents.
     *
     * <p>The {@code NamedUpdateAgents} instance can still be used.</p>
     */
    public void disposeAgents() {
        if (_agents == null)
            return;

        List<UpdateAgent<?>> agents;

        //noinspection SynchronizeOnNonFinalField
        synchronized (_agents) {
            agents = new ArrayList<>(_agents.values());
        }

        for (UpdateAgent<?> agent : agents) {
            agent.dispose();
        }

        _recentName = null;
        _recent = null;
        _agents.dispose();
        _agents = null;
    }
}

@@ -4156,32 +4156,499 @@
 n agent;%0A    %7D%0A%0A
+    /**%0A     * Update an agents subscribers.%0A     *%0A     * @param name      The name of the agent.%0A     * @param argument  The update argument.%0A     *%0A     * @param %3CT%3E  The argument type.%0A     */%0A    public %3CT%3E void update(String name, T argument) %7B%0A        PreCon.notNull(name);%0A%0A        if (!hasAgent(name))%0A            return;%0A%0A        @SuppressWarnings(%22unchecked%22)%0A        UpdateAgent%3CT%3E agent = (UpdateAgent%3CT%3E)_recent;%0A%0A        agent.update(argument);%0A    %7D%0A%0A
     /**%0A     * D

add NamedUpdateAgents#update convenience method