/*
 * ApplicationContextChangedInvoker.java  $Revision: 1.16 $  $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.Timer;
import java.util.TimerTask;
import java.util.logging.Level;

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

/**
 * @version $Revision: 1.16 $ $Date: 2007/10/03 04:39:39 $
 * @author Rob Richards
 */
public class ApplicationContextChangedInvoker extends ReflectInvoker
    implements ApplicationContextListener
{
    private static final String TARGET_KEY  = "target";
    private static final String FORWARD_KEY = "forward";
    private static final String LOAD_KEY    = "loadElements";

    private String[][]  mForwards;
    private String[]    mElements;
    private Timer       mTimer;
    private boolean     mTriggerMeter = true;
    private String      mInvokeCondition;

    public ApplicationContextChangedInvoker(ApplicationElementContext context)
    {
        super(context);

        mElements = PropertiesHelper.getStringArray(mConfig, LOAD_KEY);

        String[] forwards = PropertiesHelper.getStringArray(mConfig, FORWARD_KEY);
        if ((forwards != null) && (forwards.length > 0))
        {
            mForwards = new String[forwards.length][];
            for (int i=0 ; i < forwards.length ; i++)
            {
                String key = forwards[i];
                String value = null;
                int idx = key.indexOf('=');
                if (idx > 0)
                {
                    value = "";
                    if (key.length()-1 > idx)
                    {
                        value = key.substring(idx+1);
                    }
                    key = key.substring(0, idx);
                }

                mForwards[i] = new String[]{key, value};
            }
        }

        long meter = PropertiesHelper.getLong(mConfig, "meterInterval");
        if (meter > 0)
        {
            mTimer = new Timer(context.getElementId() + "-MeterTimerThread", true);
            TimerTask task = new MeterTimerTask(this);
            mTimer.scheduleAtFixedRate(task, 0, meter);
        }

        mInvokeCondition = PropertiesHelper.getString(mConfig, "invokeCondition", null);
    }

    public void applicationContextChanged(ApplicationContextEvent e)
    {
        if (triggerMeter())
        {
            boolean doInvoke = true;
            if (mInvokeCondition != null)
            {
                Object target = e.getValue();
                Object obj = BeanUtil.translateValue(new BeanContext(mElementContext.getApplicationContext(), this, target, mInvokeCondition));
                if (obj == null)
                {
                    doInvoke = false;
                }
                else if ((obj instanceof Boolean) && !((Boolean)obj).booleanValue())
                {
                    doInvoke = false;
                }
            }

            if (doInvoke)
            {
                if (mElements != null)
                {
                    loadElements(e, mElements);
                }

                if (mForwards != null)
                {
                    forward(e, mForwards);
                }
                else
                {
                    invokeMethod(e);
                }
            }
        }
    }
    
    protected void loadElements(ApplicationContextEvent e, String[] elements)
    {
        ApplicationContext context = e.getApplicationContext();
        for (int i=0 ; i < elements.length ; i++)
        {
            String element = elements[i];
            context.getElementManager().getApplicationElement(element);
        }
    }

    protected void forward(ApplicationContextEvent e, Object[][] forwards)
    {
        ApplicationContext context = e.getApplicationContext();
        for (int i=0 ; i < forwards.length ; i++)
        {
            Object[] forward = forwards[i];
            Object val = BeanUtil.translateValue(new BeanContext(e.getApplicationContext(), getTarget(), e.getValue(), forward[1]));
            context.contextChanged(forward[0], val);
        }
    }

    protected void invokeMethod(ApplicationContextEvent e)
    {
        String method = PropertiesHelper.getString(mConfig, "method", null);
        boolean noarg = false;
        if (method.endsWith("()"))
        {
            noarg = true;
            method = method.substring(0, method.length()-2);
        }

        Object[] args = null;
        try
        {
            Object target = getTarget();

            if (!noarg)
            {
                String arg = PropertiesHelper.getString(mConfig, "method.arg", null);
                if (arg != null)
                {
                    args = new Object[] {arg};
                }
                else
                {
                    args = PropertiesHelper.getEnumeratedStrings(mConfig, "method.arg");
                    if (args != null)
                    {
                        Object[] ar = new Object[args.length];
                        System.arraycopy(args,0, ar,0,ar.length);
                        args = ar;
                    }
                }

                if (args != null)
                {
                    Object value = e.getValue();
                    for (int i=0 ; i < args.length ; i++)
                    {
                        if (args[i] != null)
                        {
                            args[i] = BeanUtil.translateValue(new BeanContext(e.getApplicationContext(), target, value, args[i]));
                        }
                    }
                }
                else if (e.getValue() != null)
                {
                    args = new Object[]{e.getValue()};
                }
            }

            Object invokeContext = e.getValue();
            String eval = PropertiesHelper.getString(mConfig, "invokeContext", null);
            if (eval != null)
            {
                invokeContext = BeanUtil.translateValue(new BeanContext(e.getApplicationContext(), target, invokeContext, invokeContext));
            }

            invokeMethod(target, method, args, invokeContext);
        }
        catch (Throwable ex)
        {
            handleapplicationContextChangedError(e, method, args, ex);
        }
    }

    protected Object getTarget()
    {
        Object target = null;

        Object key = mConfig.get(TARGET_KEY);
        if (key != null)
        {
            String methodName = "get" + String.valueOf(key);
            try
            {
                target = ReflectUtil.invokeMethod(mElementContext.getApplicationContext(), methodName);
            }
            catch (NoSuchMethodException ex)
            {
                // ignored
            }
            catch (IllegalAccessException ex)
            {
                // ignored
            }
            catch (InvocationTargetException ex)
            {
                // ignored
            }

            if (target == null)
            {
                target = mElementContext.getApplicationContext().getElementManager().getApplicationElement(String.valueOf(key));
            }
        }

        if (target == null)
        {
            key = mElementContext.getApplicationContext().getConfiguration().get(TARGET_KEY);
            if (key != null)
            {
                target = mElementContext.getApplicationContext().getElementManager().getApplicationElement(String.valueOf(key));
            }

            if (target == null)
            {
                target = mElementContext.getApplicationContext().getApplication();
            }
        }

        return target;
    }

    protected void handleapplicationContextChangedError(ApplicationContextEvent event, String method, Object[] args, Throwable error)
    {
        mElementContext.getApplicationContext().getLogger().log(Level.WARNING, "Error calling method: "+method, error);
    }

    private void triggerMeter(boolean val)
    {
        if (mTimer != null)
        {
            synchronized(mTimer)
            {
                mTriggerMeter = val;
            }
        }
    }

    private boolean triggerMeter()
    {
        boolean ret = true;
        if (mTimer != null)
        {
            synchronized(mTimer)
            {
                ret = mTriggerMeter;
                mTriggerMeter = false;
            }
        }
        return ret;
    }

    public static class MeterTimerTask extends TimerTask
    {
        protected ApplicationContextChangedInvoker    mInvoker;
        public MeterTimerTask(ApplicationContextChangedInvoker invoker)
        {
            mInvoker = invoker;
        }

        public void run()
        {
            mInvoker.triggerMeter(true);
        }
    }
}



