package com.linkedin.restli.client;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

import com.linkedin.common.callback.Callback;
import com.linkedin.common.util.None;
import com.linkedin.r2.message.RequestContext;
import com.linkedin.r2.message.rest.RestResponse;
import com.linkedin.r2.transport.common.Client;
import com.linkedin.restli.client.multiplexer.MultiplexedRequest;
import com.linkedin.restli.client.multiplexer.MultiplexedResponse;


/**
 * Decorator Rest.li {@link com.linkedin.restli.client.Client} implementation that can capture {@link RequestContext} for
 * each {@link Request} sent and also can optionally apply some transformation on the returned response.
 * The #sendRequest operation is eventually delegated to the decorated Rest.li {@link com.linkedin.restli.client.Client}.
 */
class CapturingRestClient extends RestClient {

  private final Map<Request<?>, RequestContext> _capturedRequestContexts = new ConcurrentHashMap<>();
  private final Function<Object, Object> _responseTransformer;

  @SuppressWarnings("unchecked")
  private <T> T transformeResponse(T value) {
    return (T) _responseTransformer.apply(value);
  }

  private <T> Callback<Response<T>> withTransformationResponse(Callback<Response<T>> callback) {
    return new Callback<Response<T>>() {

      @Override
      public void onSuccess(Response<T> result) {
        callback.onSuccess(transformeResponse(result));
      }

      @Override
      public void onError(Throwable e) {
        callback.onError(e);
      }
    };
  }

  private Callback<RestResponse> withTransformationRestResponse(Callback<RestResponse> callback) {
    return new Callback<RestResponse>() {

      @Override
      public void onSuccess(RestResponse result) {
        callback.onSuccess(transformeResponse(result));
      }

      @Override
      public void onError(Throwable e) {
        callback.onError(e);
      }
    };
  }

  private Callback<MultiplexedResponse> withTransformationMultiplexedResponse(Callback<MultiplexedResponse> callback) {
    return new Callback<MultiplexedResponse>() {

      @Override
      public void onSuccess(MultiplexedResponse result) {
        callback.onSuccess(transformeResponse(result));
      }

      @Override
      public void onError(Throwable e) {
        callback.onError(e);
      }
    };
  }


  public Map<Request<?>, RequestContext> getCapturedRequestContexts() {
    return _capturedRequestContexts;
  }

  public void clearCapturedRequestContexts() {
    _capturedRequestContexts.clear();
  }

  private final RestClient _delegate;

  public int hashCode() {
    return _delegate.hashCode();
  }

  public boolean equals(Object obj) {
    return _delegate.equals(obj);
  }

  public void shutdown(Callback<None> callback) {
    _delegate.shutdown(callback);
  }

  public <T> ResponseFuture<T> sendRequest(Request<T> request, RequestContext requestContext) {
    _capturedRequestContexts.put(request, requestContext);
    return _delegate.sendRequest(request, requestContext);
  }

  public <T> ResponseFuture<T> sendRequest(Request<T> request, RequestContext requestContext,
      ErrorHandlingBehavior errorHandlingBehavior) {
    _capturedRequestContexts.put(request, requestContext);
    return _delegate.sendRequest(request, requestContext, errorHandlingBehavior);
  }

  public <T> ResponseFuture<T> sendRequest(RequestBuilder<? extends Request<T>> requestBuilder,
      RequestContext requestContext) {
    return _delegate.sendRequest(requestBuilder, requestContext);
  }

  public <T> ResponseFuture<T> sendRequest(RequestBuilder<? extends Request<T>> requestBuilder,
      RequestContext requestContext, ErrorHandlingBehavior errorHandlingBehavior) {
    return _delegate.sendRequest(requestBuilder, requestContext, errorHandlingBehavior);
  }

  public String toString() {
    return _delegate.toString();
  }

  public <T> void sendRequest(Request<T> request, RequestContext requestContext, Callback<Response<T>> callback) {
    _capturedRequestContexts.put(request, requestContext);
    _delegate.sendRequest(request, requestContext, withTransformationResponse(callback));
  }

  public <T> void sendRestRequest(Request<T> request, RequestContext requestContext,
      Callback<RestResponse> callback) {
    _capturedRequestContexts.put(request, requestContext);
    _delegate.sendRestRequest(request, requestContext, withTransformationRestResponse(callback));
  }

  public <T> void sendRequest(RequestBuilder<? extends Request<T>> requestBuilder, RequestContext requestContext,
      Callback<Response<T>> callback) {
    _delegate.sendRequest(requestBuilder, requestContext, withTransformationResponse(callback));
  }

  public <T> ResponseFuture<T> sendRequest(Request<T> request) {
    return _delegate.sendRequest(request);
  }

  public <T> ResponseFuture<T> sendRequest(Request<T> request, ErrorHandlingBehavior errorHandlingBehavior) {
    return _delegate.sendRequest(request, errorHandlingBehavior);
  }

  public <T> ResponseFuture<T> sendRequest(RequestBuilder<? extends Request<T>> requestBuilder) {
    return _delegate.sendRequest(requestBuilder);
  }

  public <T> ResponseFuture<T> sendRequest(RequestBuilder<? extends Request<T>> requestBuilder,
      ErrorHandlingBehavior errorHandlingBehavior) {
    return _delegate.sendRequest(requestBuilder, errorHandlingBehavior);
  }

  public <T> void sendRequest(Request<T> request, Callback<Response<T>> callback) {
    _delegate.sendRequest(request, withTransformationResponse(callback));
  }

  public <T> void sendRequest(RequestBuilder<? extends Request<T>> requestBuilder, Callback<Response<T>> callback) {
    _delegate.sendRequest(requestBuilder, withTransformationResponse(callback));
  }

  public void sendRequest(MultiplexedRequest multiplexedRequest) {
    _delegate.sendRequest(multiplexedRequest);
  }

  public void sendRequest(MultiplexedRequest multiplexedRequest, Callback<MultiplexedResponse> callback) {
    _delegate.sendRequest(multiplexedRequest, withTransformationMultiplexedResponse(callback));
  }

  public CapturingRestClient(Client client, String uriPrefix, RestClient delegate) {
    this(client, uriPrefix, delegate, Function.identity());
  }

  public CapturingRestClient(Client client, String uriPrefix, RestClient delegate, Function<Object, Object> responseTransformer) {
    super(client, uriPrefix);
    _responseTransformer = responseTransformer;
    _delegate = delegate;
  }

}
