/*
 * DefaultApplicationContext.java  $Revision: 1.5 $  $Date: 2007/10/03 04:39:39 $
 *
 * Copyright (C) 2007 Meldcraft Corporation
 *
 * This file is part of Scaffold.
 * 
 * Scaffold is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Scaffold 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 Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Scaffold.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.meldcraft.application;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.meldcraft.util.ReflectUtil;
import com.meldcraft.util.logging.LogUtil;

/**
 * @version $Revision: 1.5 $ $Date: 2007/10/03 04:39:39 $
 * @author Rob Richards
 */
public class DefaultApplicationContext implements ApplicationContext
{
// Static variables
    private static final Object ALL_KEY = new Object();

// Member variables
    private Application                 mApp;
    private Map                         mConfig;
    private Logger                      mLogger;
    private ApplicationElementManager   mElementManager;
    private Map                         mListenerMap = new HashMap();
    private Logger                      mPrivateLogger = LogUtil.getLogger(this);
    private String                      mLogPrefix = ReflectUtil.getSimpleClassName(getClass()) + " ";

// Constructors and initialization
    public DefaultApplicationContext(Application app, Map config)
    {
        this(app, config, LogUtil.getLogger(app));
    }

    public DefaultApplicationContext(Application app, Map config, Logger logger)
    {
        mApp = app;
        mConfig = config;
        mElementManager = new DefaultApplicationElementManager(this);
        mLogger = logger;
    }

    public DefaultApplicationContext(Application app, Map config,
                                     ApplicationElementManager elementManager, Logger logger)
    {
        mApp = app;
        mConfig = config;
        mElementManager = elementManager;
        mLogger = logger;
    }

// Interface implemenations
// Public methods
    /** Returns the application for this context. */
    public Application getApplication()
    {
        return mApp;
    }

    /** Returns the configuration for this context. */
    public Map getConfiguration()
    {
        return mConfig;
    }

    /** Returns the logger for this context. */
    public Logger getLogger()
    {
        return mLogger;
    }

    /**
     * Returns the managed object for name.
     */
    public ApplicationElementManager getElementManager()
    {
        return mElementManager;
    }

    /** Sends context changed event to listeners. */
    public void contextChanged(Object key, Object value)
    {
        contextChanged(key, value, true);
    }

    /** Adds the ApplicationContextListener. */
    public void addApplicationContextListener(ApplicationContextListener listener)
    {
        addApplicationContextListener(listener, null);
    }

    /** Removes the ApplicationContextListener. */
    public boolean removeApplicationContextListener(ApplicationContextListener listener)
    {
        return removeApplicationContextListener(listener, null);
    }

    /** Adds the ApplicationContextListener. */
    public void addApplicationContextListener(ApplicationContextListener listener, Object key)
    {
        if (key == null)
        {
            key = ALL_KEY;
        }

        synchronized(mListenerMap)
        {
            ArrayList list = (ArrayList)mListenerMap.get(key);
            if (list == null)
            {
                list = new ArrayList();
                mListenerMap.put(key, list);
            }
            list.add(listener);
        }
    }

    /** Removes the ApplicationContextListener. */
    public boolean removeApplicationContextListener(ApplicationContextListener listener, Object key)
    {
        boolean ret = false;

        if (key == null)
        {
            key = ALL_KEY;
        }

        synchronized(mListenerMap)
        {
            ArrayList list = (ArrayList)mListenerMap.get(key);
            if (list != null)
            {
                ret = list.remove(listener);
            }
        }

        return ret;
    }

// Protected methods
    protected void contextChanged(Object key, Object value,
                                     boolean fireApplicationContextChanged)
    {
        if (fireApplicationContextChanged)
        {
            notifyListeners(key, new ApplicationContextEvent(this, key, value));
        }
    }

    protected void notifyListeners(Object key, ApplicationContextEvent e)
    {
        if (mPrivateLogger.isLoggable(Level.INFO))
        {
            mPrivateLogger.info(mLogPrefix + BasicApplicationLogger.basicLogString(e));
        }

        if (key == null)
        {
            key = ALL_KEY;
        }

        ApplicationContextListener[] lista = null;
        synchronized(mListenerMap)
        {
            List listeners = (ArrayList)mListenerMap.get(key);
            if (key != ALL_KEY)
            {
                ArrayList list = (ArrayList)mListenerMap.get(ALL_KEY);
                if (list != null)
                {
                    if (listeners != null)
                    {
                        ArrayList combined = new ArrayList();
                        IdentityHashMap map = new IdentityHashMap();
                        for (int i=0 ; i < listeners.size() ; i++)
                        {
                            Object obj = listeners.get(i);
                            map.put(obj, obj);
                            combined.add(obj);
                        }
                        for (int i=0 ; i < list.size() ; i++)
                        {
                            Object obj = list.get(i);
                            if (map.get(obj) == null)
                            {
                                combined.add(obj);
                            }
                        }
                        
                        listeners = combined;
                    }
                    else
                    {
                        listeners = list;
                    }
                }
            }

            if (listeners != null)
            {
                lista = (ApplicationContextListener[])listeners.toArray(new ApplicationContextListener[listeners.size()]);
            }
        }

        if (lista != null)
        {
            for (int i=0 ; i < lista.length ; i++)
            {
                try
                {
                    lista[i].applicationContextChanged(e);
                }
                catch (Throwable t)
                {
                    mLogger.log(Level.WARNING, "Error during notify of application context event.", t);
                }
            }
        }
    }

// Private methods
// Inner classes
}
