package de.renew.remote;

import java.io.IOException;
import java.io.ObjectOutput;
import java.lang.reflect.Field;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import de.renew.engine.thread.SimulationThreadPool;
import de.renew.net.NetInstance;
import de.renew.simulatorontology.simulation.SimulationEnvironment;
import de.renew.unify.Aggregate;
import de.renew.util.TextToken;


/**
 * This class implements the <code>ObjectAccessor</code>
 * interface and nothing more.
 *
 * @author Thomas Jacob
 * @since Renew 1.6
 **/
public class ObjectAccessorImpl extends UnicastRemoteObject implements ObjectAccessor {
    /**
     * The LOGGER variable is a static instance of {@link org.apache.log4j.Logger}
     * used for logging purposes in the {@code ObjectAccessorImpl} class.
     *
     * This logger is used to record informational, debug, or error messages
     * throughout the execution of various methods within the class. It is
     * configured to use the fully qualified class name of {@code ObjectAccessorImpl}
     * as its logger name.
     *
     * The usage of this logger promotes consistent and centralized logging
     * practices, aiding in the diagnosis and monitoring of application behavior.
     */
    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(ObjectAccessorImpl.class);


    /**
     * The object for the accessor.
     **/
    protected final Object _object;

    /**
     * The simulation environment this object is situated in.
     **/
    protected final SimulationEnvironment _environment;

    /**
     * Creates a new <code>ObjectAccessor</code> for the given object.
     * <p>
     * This constructor is protected to ensure that no
     * <code>ObjectAccessor</code> instances are created for
     * <code>null</code> references.
     * Use {@link #createObjectAccessor} instead.
     * </p>
     *
     * @param object       the object for the accessor.
     *
     * @param environment  the simulation environment where this
     *                     object is situated in.
     *
     * @exception RemoteException
     *   if a RMI failure occured.
     **/
    protected ObjectAccessorImpl(Object object, SimulationEnvironment environment)
        throws RemoteException
    {
        super(0, SocketFactoryDeterminer.getInstance(), SocketFactoryDeterminer.getInstance());
        assert object != null : "ObjectAccessor instance for null reference is not allowed.";
        this._object = object;
        this._environment = environment;
    }

    /**
     * Creates a new <code>ObjectAccessor</code> for the given object.
     *
     * @param object       the object for the accessor.
     *
     * @param environment  the simulation environment where this
     *                     object is situated in.
     *
     * @return the new object accessor instance.
     *         Returns <code>null</code>, if <code>object == null</code>.
     *
     * @exception RemoteException
     *   if a RMI failure occured.
     **/
    public static ObjectAccessor createObjectAccessor(
        Object object, SimulationEnvironment environment) throws RemoteException
    {
        if (object == null) {
            return null;
        } else {
            return new ObjectAccessorImpl(object, environment);
        }
    }

    /**
     * Returns the object wrapped by this object accessor.
     * This method is available only in the local implementation,
     * not in the remote interface.
     *
     * @return  the wrapped object.
     **/
    public Object getObject() {
        return _object;
    }

    /**
     * @see ObjectAccessor#asNetInstance()
     **/
    @Override
    public NetInstanceAccessor asNetInstance() throws RemoteException, ClassCastException {
        return new NetInstanceAccessorImpl((NetInstance) _object, _environment);
    }

    /**
     * @see ObjectAccessor#asAggregate()
     **/
    @Override
    public AggregateAccessor asAggregate() throws RemoteException, ClassCastException {
        return new AggregateAccessorImpl((Aggregate) _object, _environment);
    }

    /**
     * @see ObjectAccessor#asTextToken()
     **/
    @Override
    public TextTokenAccessor asTextToken() throws RemoteException, ClassCastException {
        return new TextTokenAccessorImpl((TextToken) _object, _environment);
    }

    /**
     * @see ObjectAccessor#asString()
     **/
    @Override
    public String asString() throws RemoteException {
        return _object.toString();
    }

    /**
     * @see ObjectAccessor#getFieldCount()
     **/
    @Override
    public int getFieldCount() throws RemoteException {
        return _object.getClass().getFields().length;
    }

    /**
     * @see ObjectAccessor#getFieldNames()
     **/
    @Override
    public String[] getFieldNames() throws RemoteException {
        Field[] fields = _object.getClass().getFields();
        String[] names = new String[fields.length];
        for (int fieldNr = 0; fieldNr < fields.length; fieldNr++) {
            names[fieldNr] = fields[fieldNr].getName();
        }
        return names;
    }

    /**
     * @see ObjectAccessor#getFieldValues()
     **/
    @Override
    public String[] getFieldValues() throws RemoteException, IllegalAccessException {
        Future<String[]> future =
            SimulationThreadPool.getCurrent().submitAndWait(new Callable<String[]>()
            {
                @Override
                public String[] call() throws Exception {
                    Field[] fields = _object.getClass().getFields();
                    String[] values = new String[fields.length];
                    for (int fieldNr = 0; fieldNr < fields.length; fieldNr++) {
                        values[fieldNr] = fields[fieldNr].get(_object).toString();
                    }
                    return values;
                }
            });
        try {
            return future.get();
        } catch (InterruptedException e) {
            LOGGER.error("Timeout while waiting for simulation thread to finish", e);
        } catch (ExecutionException e) {
            LOGGER.error("Simulation thread threw an exception", e);
        }

        // We should never return nothing but some error occured befor.
        return null;
    }

    /**
     * @see ObjectAccessor#getField(int)
     **/
    @Override
    public ObjectAccessor getField(int i) throws RemoteException, IllegalAccessException {
        return createObjectAccessor(_object.getClass().getFields()[i].get(_object), _environment);
    }

    /**
     * @see ObjectAccessor#isInstanceOf(Class)
     **/
    @Override
    public boolean isInstanceOf(Class<?> testClass) throws RemoteException {
        return testClass.isInstance(_object);
    }

    /**
     * Serializes the object contained in this accessor
     * to a given object output stream.
     *
     * @param out    The stream to write to.
     *
     * @exception IOException
     *   if an I/O problem occurs.
     **/
    public void writeTo(ObjectOutput out) throws IOException {
        out.writeObject(_object);
    }

    /**
     * Returns the simulation environment where the object
     * wrapped by this accessor is situated in.
     * This method is available only in the local implementation,
     * not in the remote interface.
     *
     * @return  the simulation environment the object is situated
     *          in.
     **/
    protected SimulationEnvironment getEnvironment() {
        return _environment;
    }
}