/*
 * ActionFactory.java  $Revision: 1.18 $  $Date: 2007/10/03 04:59:32 $
 *
 * 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.swing.action;

import java.awt.event.KeyEvent;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.KeyStroke;

import com.meldcraft.application.ApplicationContext;
import com.meldcraft.application.ManagedElementFactory;
import com.meldcraft.application.swing.SwingApplicationConstants;
import com.meldcraft.gui.swing.SwingUtil;
import com.meldcraft.util.ConfigurationManager;
import com.meldcraft.util.PropertiesHelper;
import com.meldcraft.util.ReflectUtil;

/**
 * @version $Revision: 1.18 $  $Date: 2007/10/03 04:59:32 $
 * @author Rob Richards
 */

public class ActionFactory
{
//  Static variables
    public static final String ELEMENT_ID_KEY = "Application.elementId";

//  Member variables
    protected ApplicationContext    mApplicationContext;

//  Constructors and initialization
    public ActionFactory(ApplicationContext context)
    {
        this.mApplicationContext = context;
    }

//  Public methods
    public static ActionFactory createActionFactory(ApplicationContext context)
    {
        ActionFactory factory;
        synchronized(ActionFactory.class)
        {
            Map config = context.getConfiguration();
            String className = PropertiesHelper.getString(config, "ActionFactory.class", null);
            if (className == null)
            {
                config = ConfigurationManager.getConfiguration(ActionFactory.class);
                className = PropertiesHelper.getString(config, "ActionFactory.class", ActionFactory.class.getName());
            }

            try
            {
                factory = (ActionFactory)ReflectUtil.createInstance(className, ApplicationContext.class, context);
            }
            catch (Throwable e)
            {
                throw new RuntimeException("Error creating ActionFactory of class "+className, e);
            }
        }
        return factory;
    }

    public Action createAction(String actionId)
    {
        Action action = null;

        Map config = getActionConfig(mApplicationContext.getConfiguration(), actionId);
        if (config != null)
        {
            String actionClass = PropertiesHelper.getString(config, SwingApplicationConstants.ACTION_CLASS, null);
            if (actionClass == null)
            {
                actionClass = DefaultApplicationAction.class.getName();
            }

            Map defaults = new HashMap();
            defaults.put("className", actionClass);
            action = (Action)ManagedElementFactory.getInstance(mApplicationContext).createManagedElement(actionId, defaults, null);
            if (action == null)
            {
                try
                {
                    // two arg constructor
                    action = (Action)ReflectUtil.createInstance(actionClass, new Class[]{ApplicationContext.class, Map.class}, new Object[]{mApplicationContext, config});
                }
                catch (Exception e)
                {
                    try
                    {
                        // single arg constructor
                        action = (Action)ReflectUtil.createInstance(actionClass, ApplicationContext.class, mApplicationContext);
                    }
                    catch (Exception e2)
                    {
                        // no arg constructor
                        try
                        {
                            action = (Action)ReflectUtil.createInstance(actionClass);
                        }
                        catch (Exception e3)
                        {
                            throw new RuntimeException("Error creating action for "+actionId);
                        }
                    }
                }

                if (action instanceof AbstractAction)
                {
                    configureAction(mApplicationContext, actionId, config, (AbstractAction)action);
                }
            }
        }

        return action;
    }

    static void configureAction(ApplicationContext context, String elementId, Map config, AbstractAction absa)
    {
        absa.putValue(ELEMENT_ID_KEY, elementId);
        String name = PropertiesHelper.getString(config, SwingApplicationConstants.ACTION_NAME, elementId);
        if ((name != null) && !name.trim().equals(""))
        {
            NameMnemonic nm = getNameMnemonic(name);
            absa.putValue(AbstractAction.NAME, nm.getName());
            absa.putValue(AbstractAction.MNEMONIC_KEY, nm.getMnemonic());
            absa.putValue(SwingApplicationConstants.ACTION_MNEMONIC_INDEX, nm.getMnemonicIndex());
        }

        String[] accels = PropertiesHelper.getStringArray(config, SwingApplicationConstants.ACTION_ACCELERATOR, "|");
        if (accels != null) 
        {
            absa.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(accels[0]));
            if (accels.length > 1)
            {
                KeyStroke[] keyStrokes = new KeyStroke[accels.length];
                for (int i=0 ; i < keyStrokes.length ; i++)
                {
                    keyStrokes[i] = KeyStroke.getKeyStroke(accels[i]);
                }
                absa.putValue(AbstractAction.ACCELERATOR_KEY + "s", keyStrokes);
            }
        }

        String shortDesc = PropertiesHelper.getString(config, SwingApplicationConstants.ACTION_SHORT_DESCRIPTION, null);
        if (shortDesc != null)
        {
            absa.putValue(AbstractAction.SHORT_DESCRIPTION, shortDesc);
        }

        String longDesc = PropertiesHelper.getString(config, SwingApplicationConstants.ACTION_LONG_DESCRIPTION, null);
        if (longDesc != null)
        {
            absa.putValue(AbstractAction.LONG_DESCRIPTION, longDesc);
        }

        String smallIcon = PropertiesHelper.getString(config, SwingApplicationConstants.ACTION_SMALL_ICON, null);
        if (smallIcon != null)
        {
            URL url = context.getApplication().getClass().getClassLoader().getResource(smallIcon);
            ImageIcon icon = SwingUtil.loadImageIcon(url);
            absa.putValue(AbstractAction.SMALL_ICON, icon);
        }
        else if (absa.getValue(AbstractAction.NAME) != null)
        {
            String[] names = new String[] {elementId, String.valueOf(absa.getValue(AbstractAction.NAME))};
            for (int nidx=0 ; nidx < names.length ; nidx++)
            {
                ImageIcon icon = null;
                ImageIcon iconp = null;
                ImageIcon iconr = null;
                ImageIcon icons = null;

                String iname = "ai" + names[nidx];
                String[] suf = {"", "16", "24", "32"};

                Object target = null;
                String str = PropertiesHelper.getString(context.getConfiguration(), "target", null);
                if (str != null)
                {
                    target = context.getElementManager().getApplicationElement(str);
                }
                if (target == null)
                {
                    target = context.getApplication();
                }

                Object[] targets;
                String[] bname = PropertiesHelper.getStringArray(context.getConfiguration(), "baseResourceLayers");
                if (bname != null)
                {
                    targets = new Object[bname.length + 1];
                    System.arraycopy(bname,0, targets,0,bname.length);
                    targets[targets.length-1] = target;
                }
                else
                {
                    targets = new Object[]{target};
                }

                for (int i=0 ; i < targets.length ; i++)
                {
                    target = targets[i];
                    ClassLoader loader = (target instanceof String) ? context.getApplication().getClass().getClassLoader() : target.getClass().getClassLoader();
                    for (int j=0 ; (icon == null) && (j < suf.length) ; j++)
                    {
                        int count = 0;
                        if (icon == null)
                        {
                            count++;
                            icon = SwingUtil.loadResourceImage(loader, target, iname, suf[j]);
                        }
                        if (iconp == null)
                        {
                            count++;
                            iconp = SwingUtil.loadResourceImage(loader, target, iname + "p", suf[j]);
                        }
                        if (iconr == null)
                        {
                            count++;
                            iconr = SwingUtil.loadResourceImage(loader, target, iname + "r", suf[j]);
                        }
                        if (icons == null)
                        {
                            count++;
                            icons = SwingUtil.loadResourceImage(loader, target, iname + "s", suf[j]);
                        }

                        if (count == 0)
                        {
                            break;  // have all icons
                        }
                    }
                }

                if (icon != null)
                {
                    absa.putValue(AbstractAction.SMALL_ICON, icon);
                }
                if (iconp != null)
                {
                    absa.putValue(AbstractAction.SMALL_ICON + "p", iconp);
                }
                if (iconr != null)
                {
                    absa.putValue(AbstractAction.SMALL_ICON + "r", iconr);
                }
                if (icons != null)
                {
                    absa.putValue(AbstractAction.SMALL_ICON + "s", icons);
                }
            }
        }

        String command = PropertiesHelper.getString(config, SwingApplicationConstants.ACTION_COMMAND, null);
        if (command != null)
        {
            absa.putValue(AbstractAction.ACTION_COMMAND_KEY, command);
        }
    }

    public static NameMnemonic getNameMnemonic(String name)
    {
        Integer retMnemonic = null;
        Integer retMnemonicIndex = null;

        if ((name != null) && !name.equals(""))
        {
            int mnemonicIndex = Integer.MIN_VALUE;
            String mnemonic = null;
            int idx = 0;
            char denote = '_';
            while ((idx = name.indexOf(denote, idx)) != -1)
            {
                if (idx == name.length()-1)
                {
                    // end of string
                    break;
                }

                if (name.charAt(idx+1) != denote)
                {
                    // found mnemonic
                    mnemonicIndex = idx;
                    mnemonic = String.valueOf(name.charAt(idx+1));

                    if (idx == 0)
                    {
                        name = name.substring(1); 
                    }
                    else
                    {
                        name = name.substring(0,idx) + name.substring(idx+1, name.length());
                    }
                    break;
                }
                else
                {
                    // escaped denote value - condense
                    if (idx == 0)
                    {
                        name = name.substring(1); 
                    }
                    else
                    {
                        name = name.substring(0,idx) + name.substring(idx+1, name.length());
                    }
                    idx++;
                }
            }

            if (mnemonic != null)
            {
                mnemonic = mnemonic.toUpperCase();
                String vkname = "VK_" + mnemonic;

                try
                {
                    Field field = KeyEvent.class.getDeclaredField(vkname);
                    Object val = field.get(null);
                    retMnemonic = (Integer)val;
                    retMnemonicIndex = new Integer(mnemonicIndex);
                }
                catch (NoSuchFieldException e)
                {
                    // ignored
                }
                catch (IllegalArgumentException e)
                {
                    // ignored
                }
                catch (IllegalAccessException e)
                {
                    // ignored
                }
            }
        }

        return new NameMnemonic(name, retMnemonic, retMnemonicIndex);
    }

    public static Map getActionConfig(Map config, String actionId)
    {
        Map actionConfig = PropertiesHelper.getSubset(config, actionId + ".");

        if (actionConfig != null)
        {
            String[] ext = getActionExtends(config, actionId);
            if ((ext != null) && (ext.length > 0))
            {
                for (int i=0 ; i < ext.length ; i++)
                {
                    Map cfg = PropertiesHelper.getSubset(config, ext[i] + ".");
                    if (cfg.size() > 0)
                    {
                        String[] keys = (String[])cfg.keySet().toArray(new String[0]);
                        for (int j=0 ; j < keys.length ; j++)
                        {
                            String key = keys[j];
                            if (!actionConfig.containsKey(key))
                            {
                                actionConfig.put(key, cfg.get(key));
                            }
                        }
                    }
                }
            }
        }

        return actionConfig;
    }

    //  Protected methods
    protected static String[] getActionExtends(Map config, String actionId)
    {
        String[] ret = null;
        String ext = PropertiesHelper.getString(config, actionId + "." + SwingApplicationConstants.ACTION_EXTENDS, null);
        if ((ext != null) && (ext.trim().length() > 0))
        {
            StringTokenizer tok = new StringTokenizer(ext, ",");
            if (tok.countTokens() > 0)
            {
                ret = new String[tok.countTokens()];
                int idx=0;
                while (tok.hasMoreTokens())
                {
                    ret[idx++] = tok.nextToken();
                }
            }
        }
        return ret;
    }

//  Private methods
//  Inner classes
    public static class NameMnemonic
    {
        private String  mName;
        private Integer mMnemonic;
        private Integer mMnemonicIndex;

        public NameMnemonic(String name, Integer mnemonic, Integer mnemonicIndex)
        {
            mName = name;
            mMnemonic = mnemonic;
            mMnemonicIndex = mnemonicIndex;
        }

        public Integer getMnemonic()
        {
            return mMnemonic;
        }

        public Integer getMnemonicIndex()
        {
            return mMnemonicIndex;
        }

        public String getName()
        {
            return mName;
        }
    }
}



