/*
 * JBoss, Home of Professional Open Source
 * Copyright 2008-10 Red Hat and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This 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 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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 this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 * @authors Andrew Dinn
 */
package org.jboss.byteman.rule.helper;

import org.jboss.byteman.rule.type.Type;
import org.jboss.byteman.rule.Rule;
import org.jboss.byteman.rule.exception.ExecuteException;
import org.jboss.byteman.rule.binding.Bindings;
import org.jboss.byteman.rule.binding.Binding;
import org.jboss.byteman.agent.Transformer;

import java.util.HashMap;
import java.util.Iterator;

/**
 * Implementation of RuleHelper which extends the functionality of the standard helper class,
 * Helper, by adding the methods required to implement interface RuleHelper. It provides an
 * implementation which executes rules by interpreting the rule tree. Any rule which employs the
 * standard helper type checks built in method calls against class Helper. However, the interpreter
 * assumes that the helper object implements InterpretedHelper.
 *
 * When a rule is compiled class Helper is extended with a generated class CompiledHelper&lt;NNN&gt;
 * which also implements interface RuleHelper. The implementation of the execute method is generated
 * by translating the parse tree to bytecode. Builtin calls are translated to calls of methods
 * defined by class Helper.
 *
 * A rule can also specify its own helper class in order to provide its own set of builtin
 * operations. The helper class does not implement interface RuleHelper. Instead the
 * compilation process will generate a subclass of the user-defined helper class which
 * provides an appropriate implementation for the RuleHelper methods, including an implementation
 * of the execute method dreived from the rule parse tree. As in the default case, builtin calls
 * are translated to calls of methods defined by the helper class. 
 */
public class InterpretedHelper extends Helper implements HelperAdapter
{
    protected HashMap<String, Object> bindingMap;

    public InterpretedHelper(Rule rule)
    {
        super(rule);
        bindingMap = new HashMap<String, Object>();
    }

    /**
     * install values into the bindings map and then call the execute0 method
     * to actually execute the rule
     * @param recipient target of trigger method or null if it is static
     * @param args array to pass current values and return new values
     * for data local to the trigger method
     */
    public void execute(Object recipient, Object[] args)
            throws ExecuteException
    {
        Helper.verbose(rule.getName() + " execute");

        Bindings bindings = rule.getBindings();
        Iterator<Binding> iterator = bindings.iterator();
        while (iterator.hasNext()) {
            Binding binding = iterator.next();
            String name = binding.getName();
            if (binding.isAlias()) {
                // this is a local var used to refer to a method recipient or parameter
                // so use the value and type associated with the alias
                binding = binding.getAlias();
            }
            if (binding.isHelper()) {
                bindingMap.put(name, this);
            } else if (binding.isRecipient()) {
                bindingMap.put(name, recipient);
            // } else if (!binding.isParam() || binding.isLocalVar() || binding.isReturn() ||
            //         binding.isThrowable() || binding.isParamCount() || binding.isParamArray()) {
            } else if (!binding.isBindVar()) {
                bindingMap.put(name, args[binding.getCallArrayIndex()]);
            }
        }

        // now do the actual execution

        execute0();

        // now restore update bindings

        iterator = bindings.iterator();

        while (iterator.hasNext()) {
            Binding binding = iterator.next();
            String name = binding.getName();
            if (binding.isAlias()) {
                continue;
            }

            if (binding.isUpdated()) {
                // if (binding.isParam() || binding.isLocalVar() || binding.isReturn()) {
                if (!binding.isBindVar() && !binding.isAlias()) {
                    Object value = bindingMap.get(name);
                    int idx = binding.getCallArrayIndex();
                    args[idx] = value;
                }
            }
        }

    }

    /**
     * basic implementation of rule execution
     *
     * @throws ExecuteException if an exception occurs during
     * execution of the rule
     */
        
    protected void execute0()
            throws ExecuteException
    {
        // System.out.println(rule.getName() + " execute0");
        bind();
        if (test()) {
            fire();
        }
    }

    public void setBinding(String name, Object value)
    {
        bindingMap.put(name, value);
    }

    public Object getBinding(String name)
    {
        return bindingMap.get(name);
    }

    private void bind()
            throws ExecuteException
    {
        // System.out.println(rule.getName() + " bind");
        rule.getEvent().interpret(this);
    }

    private boolean test()
            throws ExecuteException
    {
        // System.out.println(rule.getName() + " test");
        return (Boolean)rule.getCondition().interpret(this);
    }
        
    private void fire()
            throws ExecuteException
    {
        // System.out.println(rule.getName() + " fire");
        rule.getAction().interpret(this);
    }

    public String getName() {
        return rule.getName();
    }

    public Object getAccessibleField(Object owner, int fieldIndex)
    {
        return rule.getAccessibleField(owner, fieldIndex);
    }

    public void setAccessibleField(Object owner, Object value, int fieldIndex)
    {
        rule.setAccessibleField(owner, value, fieldIndex);
    }
    public Object invokeAccessibleMethod(Object target, Object[] args, int methodIndex)
    {
        return rule.invokeAccessibleMethod(target, args, methodIndex);
    }
}
