/*
 * ManagedElementFactory.java  $Revision: 1.15 $  $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.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;

import com.meldcraft.application.util.BeanContext;
import com.meldcraft.application.util.BeanUtil;
import com.meldcraft.util.ConfigurationManager;
import com.meldcraft.util.PropertiesHelper;
import com.meldcraft.util.ReflectUtil;

/**
 * @version $Revision: 1.15 $ $Date: 2007/10/03 04:39:39 $
 * @author Rob Richards
 */
public class ManagedElementFactory
{
    private static Map              sFactoryMap = new HashMap();
    protected ApplicationContext    mApplicationContext;

    protected ManagedElementFactory(ApplicationContext applicationContext)
    {
        mApplicationContext = applicationContext;
    }

    public static ManagedElementFactory getInstance(ApplicationContext context)
    {
        ManagedElementFactory ret;

        synchronized(sFactoryMap)
        {
            ret = (ManagedElementFactory)sFactoryMap.get(context);

            if (ret == null)
            {
                Map config = context.getConfiguration();
                String className = PropertiesHelper.getString(config, "ManagedElementFactory.className", null);
                if (className == null)
                {
                    config = ConfigurationManager.getConfiguration(ManagedElementFactory.class);
                    className = PropertiesHelper.getString(config, "className", null);
                }

                try
                {
                    if (className == null)
                    {
                        ret = new ManagedElementFactory(context);
                    }
                    else
                    {
                        ret = (ManagedElementFactory)ReflectUtil.createInstance(className, ApplicationContext.class, context);
                    }

                    sFactoryMap.put(context, ret);
                }
                catch (Throwable e)
                {
                    throw new RuntimeException("Error creating ManagedElementFactory of class "+className, e);
                }
            }
        }

        return ret;
    }

    public Object createManagedElement(String elementId)
    {
        return createManagedElement(elementId, null, null);
    }
    
    public Object createManagedElement(String elementId, Map defaults, Map overrides)
    {
        Object ret = null;
        boolean isAnonymous = (elementId == null);

        ApplicationElementContext elementContext = new ApplicationElementContext(mApplicationContext, elementId, defaults, overrides);
        Map config = elementContext.getConfiguration();

        String cname = PropertiesHelper.getString(config, "className", null);
        if (cname != null)
        {
            // try constructor(ApplicationElementContext)
            Object[] args = new Object[] {elementContext};
            if (!isAnonymous)
            {
                ret = createElement(cname, new Class[]{ApplicationElementContext.class}, args);
            }

            if (ret == null)
            {
                // try constructor(Map,String)
                if (!isAnonymous)
                {
                    args = new Object[] {config, elementId};
                    ret = createElement(cname, new Class[]{Map.class, String.class}, args);
                }

                if (ret == null)
                {
                    // try noarg constructor()
                    args = null;
                    ret = createElement(cname, null, args);

                    if (ret != null)
                    {
                        // call applicationInitialize with ApplicationContext or config, if implemented
                        if (isAnonymous || !tryOptionalMethod(ret, config, cname, elementId, "applicationInitialize", new Object[] {elementContext}))
                        {
                            if (!tryOptionalMethod(ret, config, cname, elementId, "applicationInitialize", new Object[] {elementContext.getApplicationContext()}))
                            {
                                if (isAnonymous || !tryOptionalMethod(ret, config, cname, elementId, "applicationInitialize", new Object[] {config, elementId}))
                                {
                                    if (!tryOptionalMethod(ret, config, cname, elementId, "applicationInitialize", new Object[] {config}))
                                    {
                                        if (!tryOptionalMethod(ret, config, cname, elementId, "applicationInitialize", null))
                                        {
                                            // oh well, no applicationInitialize method
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            if (ret != null)
            {
                configureElementProperties(config, elementId, ret);
                initializeElementContextListenerInvokers(mApplicationContext, elementId);
            }
        }
        else
        {
            try
            {
                ret = Class.forName(elementId);
            }
            catch (ClassNotFoundException e)
            {
                // ignored
            }
        }

        return ret;
    }

    public void configureElementProperties(ApplicationContext context, String elementId, Object element)
    {
        if (element != null)
        {
            Map config = PropertiesHelper.getSubset(context.getConfiguration(), elementId + ".");
            configureElementProperties(config, elementId, element);
        }
    }

    protected Object createElement(String className, Class[] types, Object[] args)
    {
        Object ret = null;

        InvocationTargetException error = null;
        try
        {
            Class clazz = Class.forName(className);
            Object obj = null;
            try
            {
                // try two arg constructor
                obj = ReflectUtil.createInstance(clazz, types, args);
            }
            catch (NoSuchMethodException e)
            {
            }
            catch (InstantiationException e)
            {
            }
            catch (IllegalAccessException e)
            {
            }
            catch (InvocationTargetException e)
            {
                error = e;
            }

            if ((error == null) && (obj == null))
            {
                try
                {
                    // try static create method
                    obj = ReflectUtil.invokeMethod(clazz, "createInstance", args);
                }
                catch (NoSuchMethodException e)
                {
                }
                catch (IllegalAccessException e)
                {
                }
                catch (InvocationTargetException e)
                {
                    error = e;
                }
            }

            ret = obj;
        }
        catch (ClassNotFoundException e)
        {
        }
        finally
        {
            if (error != null)
            {
                mApplicationContext.getLogger().log(Level.SEVERE, "Error creating instance of " + className, error);
            }
        }

        return ret;
    }

    protected void initializeElementContextListenerInvokers(ApplicationContext context, String elementId)
    {
        initializeContextListenerInvokers(context, elementId);
    }

    protected void configureElementProperties(Map config, String elementId, Object element)
    {
        Map map = PropertiesHelper.getMap(config, "elementProperties");
        if (map != null)
        {
            String[] keys = (String[])map.get(PropertiesHelper.ORDERED_KEYS);
            for (int i=0 ; i < keys.length ; i++)
            {
                String property = keys[i];
                BeanUtil.setPropertyValue(new BeanContext(elementId, mApplicationContext, element, map.get(property)), property);
            }
        }

        map = PropertiesHelper.getSubset(config, "elementProperty.");
        if (map != null)
        {
            Object[] keys = map.keySet().toArray();
            for (int i=0 ; i < keys.length ; i++)
            {
                Object key = keys[i];
                if (key instanceof String)
                {
                    String property = (String)key;
                    BeanUtil.setPropertyValue(new BeanContext(elementId, mApplicationContext, element, map.get(property)), property);
                }
            }
        }
    }

    public static void initializeContextListenerInvokers(ApplicationContext context, String elementId)
    {
        Map map = PropertiesHelper.getMap(context.getConfiguration(), elementId + ".contextListenerInvokers");
        if ((map != null) && (map.size() > 0))
        {
            String defaultInvoker = PropertiesHelper.getString(map, "defaultInvokerClassName", ApplicationContextChangedInvoker.class.getName());
            String[] keys = (String[])map.get(PropertiesHelper.ORDERED_KEYS);
            for (int iter=0 ; iter < 2 ; iter++)
            {
                for (int i=0 ; i < keys.length ; i++)
                {
                    String key = keys[i];
                    if (iter == 0)
                    {
                        if (!key.equals("ALL"))
                        {
                            continue;
                        }
                    }
                    else if (key.equals("ALL"))
                    {
                        continue;
                    }

                    String[] vals = PropertiesHelper.getStringArray((String)map.get(key));
                    for (int j=0 ; j < vals.length ; j++)
                    {
                        String val = vals[j];
                        HashMap defaults = new HashMap();
                        defaults.put("className", defaultInvoker);
                        try
                        {
                            ApplicationContextListener listener = (ApplicationContextListener)ManagedElementFactory.getInstance(context).createManagedElement(val, defaults, null);
                            if (key.equals("ALL"))
                            {
                                context.addApplicationContextListener(listener);
                            }
                            else
                            {
                                context.addApplicationContextListener(listener, key);
                            }
                        }
                        catch (Throwable e)
                        {
                            context.getLogger().log(Level.WARNING, "Error initializing context listener for " + key, e);
                        }
                    }
                }
            }
        }
    }

    private boolean tryOptionalMethod(Object target, Map config, String className, String elementName, String methodName, Object[] args)
    {
        boolean ret = true;
        try
        {
            ReflectUtil.invokeMethod(target, methodName, args);
        }
        catch (NoSuchMethodException e1)
        {
            // OK - does not have to exist
            ret = false;
        }
        catch (IllegalAccessException e1)
        {
            mApplicationContext.getLogger().log(Level.SEVERE, "Error initializing element " + elementName + " of class " + className, e1);
        }
        catch (InvocationTargetException e1)
        {
            mApplicationContext.getLogger().log(Level.SEVERE, "Error initializing element " + elementName + " of class " + className, e1);
        }
        return ret;
    }
}



