package de.renew.formalism.function;

import java.io.IOException;
import java.io.Serial;
import java.lang.reflect.InvocationTargetException;

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

/**
 * A function that invokes a static method on a given class.
 */
public class DynamicStaticMethodFunction implements Function {
    /**
     * The name of the static method to be invoked
     */
    private final String _method;

    /**
     * This field is not really transient, but as <code>java.lang.Class
     * </code>is not always serializable, we have to store it by
     * ourselves.
     **/
    transient Class<?> _clazz;

    /**
     * Creates an instance of this class describing a static function in some class that is supposed to be executed using this class.
     *
     * @param method the name of the static function
     * @param clazz the name of the class containing the static function
     */
    public DynamicStaticMethodFunction(String method, Class<?> clazz) {
        this._method = method;
        this._clazz = clazz;
    }

    @Override
    public Object function(Object param) throws Impossible {
        Tuple args = (Tuple) param;

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

        try {
            return Executor.executeMethod(_clazz, null, _method, paramArr);
        } catch (Exception e) {
            if (e instanceof InvocationTargetException) {
                throw new Impossible(
                    "InvocationTargetException occured during method call: "
                        + ((InvocationTargetException) e).getTargetException());
            } else {
                throw new Impossible("Exception occured during method call: " + e);
            }
        }
    }

    /**
     * Serialization method, behaves like default writeObject
     * method. Stores the not-really-transient clazz field.
     *
     * @param out the output stream to write to
     * @throws IOException if writing the object fails
     **/
    @Serial
    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        ReflectionSerializer.writeClass(out, _clazz);
    }

    /**
     * Deserialization method, behaves like default readObject
     * method. Restores the not-really-transient clazz field.
     *
     * @param in the input stream to read from
     * @throws IOException if reading the object fails
     * @throws ClassNotFoundException if a required class is not found during reading
     **/
    @Serial
    private void readObject(java.io.ObjectInputStream in)
        throws IOException, ClassNotFoundException
    {
        in.defaultReadObject();
        _clazz = ReflectionSerializer.readClass(in);
    }
}