package games.strategy.engine.message;

import java.io.Serializable;
import java.lang.reflect.Method;

import games.strategy.engine.message.unifiedmessenger.UnifiedMessenger;
import games.strategy.triplea.util.WrappedInvocationHandler;

/**
 * Invocation handler for the UnifiedMessenger.
 *
 * <p>
 * Handles the invocation for a channel
 * </p>
 */
class UnifiedInvocationHandler extends WrappedInvocationHandler {
  private final UnifiedMessenger m_messenger;
  private final String m_endPointName;
  private final boolean m_ignoreResults;
  private final Class<?> m_remoteType;

  public UnifiedInvocationHandler(final UnifiedMessenger messenger, final String endPointName,
      final boolean ignoreResults, final Class<?> remoteType) {
    // equality and hash code are bassed on end point name
    super(endPointName);
    m_messenger = messenger;
    m_endPointName = endPointName;
    m_ignoreResults = ignoreResults;
    m_remoteType = remoteType;
  }

  @Override
  public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
    if (super.shouldHandle(method, args)) {
      return super.handle(method, args);
    }
    if (args != null) {
      for (final Object o : args) {
        if (o != null && !(o instanceof Serializable)) {
          throw new IllegalArgumentException(
              o + " is not serializable, all remote method args must be serializable.  method:" + method);
        }
      }
    }
    final RemoteMethodCall remoteMethodMsg =
        new RemoteMethodCall(m_endPointName, method.getName(), args, method.getParameterTypes(), m_remoteType);
    if (m_ignoreResults) {
      m_messenger.invoke(m_endPointName, remoteMethodMsg);
      return null;
    } else {
      final RemoteMethodCallResults response = m_messenger.invokeAndWait(m_endPointName, remoteMethodMsg);
      if (response.getException() != null) {
        if (response.getException() instanceof MessengerException) {
          final MessengerException cle = (MessengerException) response.getException();
          cle.fillInInvokerStackTrace();
        } else {
          // do not chain the exception, we want to keep whatever the original exception's class was, so just add our
          // bit to the stack
          // trace.
          final Throwable throwable = response.getException();
          final StackTraceElement[] exceptionTrace = throwable.getStackTrace();
          final Exception ourException =
              new Exception(throwable.getMessage() + " exception in response from other system");
          // Thread.currentThread().getStackTrace();
          final StackTraceElement[] ourTrace = ourException.getStackTrace();
          if (exceptionTrace != null && ourTrace != null) {
            final StackTraceElement[] combinedTrace = new StackTraceElement[(exceptionTrace.length + ourTrace.length)];
            int i = 0;
            for (final StackTraceElement element : exceptionTrace) {
              combinedTrace[i] = element;
              i++;
            }
            for (final StackTraceElement element : ourTrace) {
              combinedTrace[i] = element;
              i++;
            }
            throwable.setStackTrace(combinedTrace);
          }
        }
        throw response.getException();
      }
      return response.getRVal();
    }
  }
}
