package de.renew.formalism.function;

import java.io.IOException;
import java.io.Serial;
import java.lang.reflect.Constructor;
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 creates new object instances by invoking a specific constructor.
 */
public class ConstructorFunction implements Function {
    private static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(ConstructorFunction.class);

    /**
     * This field is not really transient, but as the reflection
     * classes are not serializable, we have to store it by
     * ourselves.
     **/
    transient Constructor<?> _constr;

    /**
     * Constructs an instance of this class for a given constructor.
     *
     * @param constructor the constructor
     */
    public ConstructorFunction(Constructor<?> constructor) {
        _constr = constructor;
    }

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

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

        try {
            return Executor.executeConstructor(_constr, paramArr);
        } catch (Exception e) {
            if (e instanceof InvocationTargetException) {
                Throwable targetException = ((InvocationTargetException) e).getTargetException();
                String baseError = "Constructor call resulted in an exception:" + targetException;
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(baseError + " while executing " + this, targetException);
                }
                throw new Impossible(baseError, targetException);
            } else {
                String baseError = "Exception occured during constructor call: " + e;
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(baseError + " while executing " + this, e);
                }
                throw new Impossible(baseError, e);
            }
        } catch (LinkageError e) {
            String baseError = "LinkageError occured during constructor call: " + e;
            LOGGER.warn(baseError + " while executing " + this, e);
            throw new Impossible(baseError, e);
        }
    }

    /**
     * Serialization method, behaves like default writeObject
     * method. Stores the not-really-transient constr 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.writeConstructor(out, _constr);
    }

    /**
     * Deserialization method, behaves like default readObject
     * method. Restores the not-really-transient constr 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();
        _constr = ReflectionSerializer.readConstructor(in);
    }

    @Override
    public final String toString() {
        return "ConstrFunc: " + _constr.toString();
    }
}