/*
 * BeanUtil.java  $Revision: 1.23 $  $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.util;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.DecimalFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.meldcraft.application.ApplicationContext;
import com.meldcraft.application.ApplicationElementContext;
import com.meldcraft.application.ManagedElementFactory;
import com.meldcraft.util.FormatterExpander;
import com.meldcraft.util.PropertiesHelper;
import com.meldcraft.util.ReflectUtil;
import com.meldcraft.util.ScriptExpander;

/**
 * @version $Revision: 1.23 $ $Date: 2007/10/03 04:59:32 $
 * @author Rob Richards
 */
public class BeanUtil
{
    protected BeanUtil()
    {
    }

    public static Object createInstance(ApplicationElementContext context)
    {
        Object ret = null;

        Map config = context.getConfiguration();

        Object target = context;
        String str = PropertiesHelper.getString(config, "targetElement", null);
        if (str != null)
        {
            target = context.getApplicationContext().getElementManager().getApplicationElement(str);
        }
        else
        {
            str = PropertiesHelper.getString(config, "targetClass", null);
            if (str != null)
            {
                try
                {
                    target = Class.forName(str);
                }
                catch (ClassNotFoundException e)
                {
                    // ignored
                }
            }
            else
            {
                str = PropertiesHelper.getString(config, "targetInstance", null);
                if (str != null)
                {
                    try
                    {
                        target = ReflectUtil.createInstance(str); 
                    }
                    catch (ClassNotFoundException e)
                    {
                        // ignored
                    }
                    catch (NoSuchMethodException e)
                    {
                    }
                    catch (InstantiationException e)
                    {
                    }
                    catch (IllegalAccessException e)
                    {
                    }
                    catch (InvocationTargetException e)
                    {
                    }
                }
            }
        }

        if (target != null)
        {
            ret = getPropertyValue(target, PropertiesHelper.getString(config, "targetProperty", null));
        }

        return ret;
    }

    public static Object getPropertyValue(Object target, String propString)
    {
        Object ret = null;

        String[] props = PropertiesHelper.getStringArray(propString, ".");
        if (props == null)
        {
            ret = target;
        }
        else
        {
            Object val = target;
            for (int i=0 ; (val != null) && (i < props.length) ; i++)
            {
                String prop = props[i];
                // TODO: handle arrays/lists, e.g. items[].subprop<-new list of item subprops, items[0], items.length/size
                if ((prop.equals("size") || prop.equals("length")) && val.getClass().isArray())
                {
                    val = new Integer(Array.getLength(val));
                }
                else if (prop.indexOf('[') > -1)
                {
                    int start = prop.indexOf('[');
                    int end = prop.indexOf(']');
                    int idx = -1;
                    if (end > start)
                    {
                        idx = PropertiesHelper.getInt(prop.substring(start+1, end), -1);
                    }

                    if (idx >= 0)
                    {
                        String pname = prop.substring(0, start);
                        Object obj = getPropertyValue(val, pname);
                        val = null;
                        if (obj != null)
                        {
                            try
                            {
                                if (obj.getClass().isArray())
                                {
                                        val = Array.get(obj, idx);
                                }
                                else if (obj instanceof List)
                                {
                                    List list = (List)obj;
                                    val = list.get(idx);
                                }
                                else if (obj instanceof Collection)
                                {
                                    Collection coll = (Collection)obj;
                                    val = coll.toArray()[idx];
                                }
                            }
                            catch (Exception e)
                            {
                            }
                        }
                    }
                    else
                    {
                        val = null;
                    }
                }
                else if (val instanceof Map)
                {
                    // value of key prop in Map
                    val = ((Map)val).get(prop);
                }
                else
                {
                    if (prop.length() > 0)
                    {
                        String meth = "get" + prop.substring(0,1).toUpperCase();
                        if (prop.length() > 1)
                        {
                            meth += prop.subSequence(1, prop.length());
                        }

                        try
                        {
                            // getProp()
                            val = ReflectUtil.invokeMethod(val, meth);
                        }
                        catch (NoSuchMethodException e)
                        {
                            try
                            {
                                // prop()
                                val = ReflectUtil.invokeMethod(val, prop);
                            }
                            catch (NoSuchMethodException e2)
                            {
                                try
                                {
                                    // public prop
                                    val = ReflectUtil.getFieldValue(val, prop);
                                }
                                catch (SecurityException e3)
                                {
                                    val = null;
                                }
                                catch (NoSuchFieldException e3)
                                {
                                    val = null;
                                }
                                catch (IllegalArgumentException e3)
                                {
                                    val = null;
                                }
                                catch (IllegalAccessException e3)
                                {
                                    val = null;
                                }
                            }
                            catch (IllegalAccessException e2)
                            {
                                val = null;
                            }
                            catch (InvocationTargetException e2)
                            {
                                val = null;
                            }
                        }
                        catch (IllegalAccessException e)
                        {
                            val = null;
                        }
                        catch (InvocationTargetException e)
                        {
                            val = null;
                        }
                    }
                }
            }
            ret = val;
        }

        return ret;
    }

    public static void setPropertyValue(BeanContext beanContext, String property)
    {
        ApplicationContext context = beanContext.getApplicationContext();
        Object target = beanContext.getTarget();
        Object value = beanContext.getValue();
        String elementId = beanContext.getTargetElementId();

        Object thisTarget = target;

        int idx = property.lastIndexOf('.');
        if (idx != -1)
        {
            // e.g. getprop.getprop2.setprop
            String get = property.substring(0, idx);
            target = getPropertyValue(target, get);
            property = (idx < property.length()-1) ? property.substring(idx+1, property.length()) : "";
        }

        if (target != null)
        {
            boolean valueIsNull = (value instanceof String) && ((String)value).equals("<null>");
            value = translateValue(new BeanContext(elementId, context, thisTarget, target, value, property));

            if (target instanceof Map)
            {
                // value of key prop in Map
                ((Map)target).put(property, value);
            }
            else
            {
                if (property.length() > 0)
                {
                    String meth = "set" + property.substring(0,1).toUpperCase();
                    if (property.length() > 1)
                    {
                        meth += property.subSequence(1, property.length());
                    }

                    Object[] varg = null;
                    if ((value != null) || valueIsNull)
                    {
                        varg = new Object[] {value};
                    }

                    try
                    {
                        // setProp(args)
                        ReflectUtil.invokeMethod(target, meth, varg);
                    }
                    catch (NoSuchMethodException e)
                    {
                        try
                        {
                            // prop(args)
                            ReflectUtil.invokeMethod(target, property, varg);
                        }
                        catch (NoSuchMethodException e2)
                        {
                            try
                            {
                                // public prop
                                ReflectUtil.setFieldValue(target, property, value); 
                            }
                            catch (SecurityException e3)
                            {
                            }
                            catch (NoSuchFieldException e3)
                            {
                            }
                            catch (IllegalArgumentException e3)
                            {
                            }
                            catch (IllegalAccessException e3)
                            {
                            }
                            catch (NoSuchMethodException e3)
                            {
                            }
                            catch (InstantiationException e3)
                            {
                            }
                            catch (InvocationTargetException e3)
                            {
                            }
                        }
                        catch (IllegalAccessException e2)
                        {
                        }
                        catch (InvocationTargetException e2)
                        {
                        }
                    }
                    catch (IllegalAccessException e)
                    {
                    }
                    catch (InvocationTargetException e)
                    {
                    }
                }
            }
        }
    }

    public static Object translateValue(BeanContext beanContext)
    {
        boolean valueSet = false;

        ApplicationContext context = beanContext.getApplicationContext();
        Object thisTarget = beanContext.getThisTarget();
        Object target = beanContext.getTarget();
        Object value = beanContext.getValue();
        ScriptExpander scripter = beanContext.getScriptExpander();

        if (value instanceof String)
        {
            FormatterExpander formatter = new FormatterExpander();
            ProxyExpander proxy = new ProxyExpander(beanContext);
            scripter.getBindings().put("applicationContext", context);
            scripter.getBindings().put("target", thisTarget);
            scripter.getBindings().put("value", target);

            String str = (String)value;
            String pre;
            if ("<null>".equals(str))
            {
                valueSet = true;
                value = null;
            }
            else if (formatter.expandFormat(str, target))
            {
                valueSet = true;
                value = formatter.getExpanded();
            }
            else if (scripter.expandScript(str))
            {
                valueSet = true;
                value = scripter.getExpanded();
            }
            else if (proxy.expandProxy(str))
            {
                valueSet = true;
                value = proxy.getExpanded();
            }
            else
            {
                pre = "<element:";
                int idx;
                if ((context != null) && str.startsWith(pre))
                {
                    idx = str.indexOf('>', pre.length());
                    if (idx > pre.length()+1)
                    {
                        valueSet = true;
                        String elementId = str.substring(pre.length(), idx);
                        value = context.getElementManager().getApplicationElement(elementId);
                        if ((value != null) && (idx < str.length() - 1))
                        {
                            String prop = str.substring(idx+1, str.length());
                            if (prop.startsWith(".") && (prop.length() > 1))
                            {
                                prop = prop.substring(1);
                            }
                            value = getPropertyValue(value, prop);
                        }
                    }
                }
                else if (str.startsWith(pre = "<this>"))
                {
                    valueSet = true;
                    if (str.length() > pre.length() + 1)
                    {
                        String prop = str.substring(pre.length());
                        if (prop.startsWith(".") && (prop.length() > 1))
                        {
                            prop = prop.substring(1);
                        }
                        value = getPropertyValue(thisTarget, prop);
                    }
                    else
                    {
                        value = thisTarget;
                    }
                }
                else if ((idx = str.indexOf(pre = "<target>")) != -1)
                {
                    valueSet = true;
                    if (str.length() > pre.length() + 1)
                    {
                        String prop = str.substring(idx + pre.length());
                        if (prop.startsWith(".") && (prop.length() > 1))
                        {
                            prop = prop.substring(1);
                        }
                        value = getPropertyValue(target, prop);
                        if (idx > 0)
                        {
                            String val = str.substring(0, idx);
                            if (value != null)
                            {
                                val += String.valueOf(value);
                            }
                            value = val;
                        }
                    }
                    else
                    {
                        value = target;
                    }
                }
            }
        }

        if (!valueSet && (value instanceof String))
        {
            String strVal = (String)value;
            if ((strVal.length() > 0) && strVal.toUpperCase().equals(value))
            {
                try
                {
                    // common conversion of int
                    value = Integer.valueOf(strVal);
                }
                catch (NumberFormatException e)
                {
                    try
                    {
                        // common conversion of double
                        value = Double.valueOf(strVal);
                    }
                    catch (NumberFormatException e2)
                    {
                        // value is possibly a constant 
                        try
                        {
                            if (thisTarget != null)
                            {
                                value = ReflectUtil.getFieldValue(thisTarget, strVal);
                                //value = String.valueOf(value);
                            }
                        }
                        catch (NoSuchFieldException e1)
                        {
                        }
                        catch (SecurityException e1)
                        {
                        }
                        catch (IllegalArgumentException e1)
                        {
                        }
                        catch (IllegalAccessException e1)
                        {
                        }
                    }
                }
            }
        }

        return value;
    }

    protected static String formatValue(String type, String format, Object value)
    {
        String ret = "";

        if (type.equals("message"))
        {
            MessageFormat fmt = new MessageFormat(format);
            if (! (value instanceof Object[]))
            {
                value = new Object[]{value};
            }
            ret = fmt.format((Object[])value);
        }
        else if (type.equals("date"))
        {
            SimpleDateFormat fmt = new SimpleDateFormat(format);
            ret = fmt.format(value);
        }
        else if (type.equals("decimal"))
        {
            DecimalFormat fmt = new DecimalFormat(format);
            ret = fmt.format(value);
        }
        
        return ret;
    }
}



