package de.renew.formalism.function;

import java.lang.reflect.InvocationTargetException;

import de.renew.expression.Function;
import de.renew.unify.Impossible;
import de.renew.unify.Tuple;

/**
 * Base class for functions that call a method on an object.
 * Subclasses must define how the method is called.
 */
public abstract class AbstractMethodFunction implements Function {
    /**
     * Default constructor for AbstractMethodFunction.
     */
    public AbstractMethodFunction() {}

    private static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(AbstractMethodFunction.class);

    /**
     * Executes the function described by this class.
     *
     * @param obj the object to execute the function on
     * @param paramArr the parameters to use during the execution
     * @return the result of the function
     * @throws Exception if the function described by this class throws an exception
     */
    public abstract Object doFunction(Object obj, Object[] paramArr) throws Exception;

    @Override
    public Object function(Object param) throws Impossible {
        Tuple tuple = (Tuple) param;
        if (tuple.getArity() != 2) {
            throw new Impossible();
        }
        Object obj = tuple.getComponent(0);
        Tuple args = (Tuple) tuple.getComponent(1);

        Object[] paramArr = new Object[args.getArity()];
        for (int i = 0; i < paramArr.length; i++) {
            paramArr[i] = args.getComponent(i);
        }

        try {
            return doFunction(obj, paramArr);
        } catch (Exception e) {
            if (e instanceof InvocationTargetException) {
                Throwable targetException = ((InvocationTargetException) e).getTargetException();
                String baseError = "Method call resulted in an exception: " + targetException;
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(
                        baseError + " while executing " + this + " on object " + obj
                            + " with parameters " + makeParamList(paramArr),
                        targetException);
                }
                throw new Impossible(baseError, targetException);
            } else {
                String baseError = "Exception occurred during method call: " + e;
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(
                        baseError + " while executing " + this + " on object " + obj
                            + " with parameters " + makeParamList(paramArr),
                        e);
                }
                throw new Impossible(baseError, e);
            }
        } catch (LinkageError e) {
            String baseError = "LinkageError occurred during method call: " + e;
            LOGGER.warn(baseError + " while executing " + this, e);
            throw new Impossible(baseError, e);
        }
    }

    private String makeParamList(Object[] paramArr) {
        if (paramArr == null) {
            return "<null>";
        }

        StringBuilder result = new StringBuilder();
        result.append("<");
        result.append(paramArr.length);
        result.append(">(");
        for (int i = 0; i < paramArr.length; i++) {
            if (i > 0) {
                result.append(", ");
            }
            result.append(paramArr[0]);
        }
        result.append(")");
        return result.toString();
    }
}