/*
 * Copyright (C) 2012 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.inject.servlet;

import com.google.common.collect.ImmutableMap;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.OutOfScopeException;
import com.google.inject.Provides;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import junit.framework.TestCase;

// TODO: Add test for HTTP transferring.
/** Tests transferring of entire request scope. */

public class TransferRequestIntegrationTest extends TestCase {
  private final Callable<Boolean> FALSE_CALLABLE =
      new Callable<Boolean>() {
        @Override
        public Boolean call() {
          return false;
        }
      };

  public void testTransferHttp_outOfScope() {
    try {
      ServletScopes.transferRequest(FALSE_CALLABLE);
      fail();
    } catch (OutOfScopeException expected) {
    }
  }

  public void testTransferNonHttp_outOfScope() {
    try {
      ServletScopes.transferRequest(FALSE_CALLABLE);
      fail();
    } catch (OutOfScopeException expected) {
    }
  }

  public void testTransferNonHttp_outOfScope_closeable() {
    try {
      ServletScopes.transferRequest();
      fail();
    } catch (OutOfScopeException expected) {
    }
  }

  public void testTransferNonHttpRequest() throws Exception {
    final Injector injector =
        Guice.createInjector(
            new AbstractModule() {
              @Override
              protected void configure() {
                bindScope(RequestScoped.class, ServletScopes.REQUEST);
              }

              @Provides
              @RequestScoped
              Object provideObject() {
                return new Object();
              }
            });

    Callable<Callable<Boolean>> callable =
        new Callable<Callable<Boolean>>() {
          @Override
          public Callable<Boolean> call() {
            final Object original = injector.getInstance(Object.class);
            return ServletScopes.transferRequest(
                new Callable<Boolean>() {
                  @Override
                  public Boolean call() {
                    return original == injector.getInstance(Object.class);
                  }
                });
          }
        };

    ImmutableMap<Key<?>, Object> seedMap = ImmutableMap.of();
    Callable<Boolean> transfer = ServletScopes.scopeRequest(callable, seedMap).call();

    ExecutorService executor = Executors.newSingleThreadExecutor();
    assertTrue(executor.submit(transfer).get());
    executor.shutdownNow();
  }

  public void testTransferNonHttpRequest_closeable() throws Exception {
    final Injector injector =
        Guice.createInjector(
            new AbstractModule() {
              @Override
              protected void configure() {
                bindScope(RequestScoped.class, ServletScopes.REQUEST);
              }

              @Provides
              @RequestScoped
              Object provideObject() {
                return new Object();
              }
            });

    class Data {
      Object object;
      RequestScoper scoper;
    }

    Callable<Data> callable =
        new Callable<Data>() {
          @Override
          public Data call() {
            Data data = new Data();
            data.object = injector.getInstance(Object.class);
            data.scoper = ServletScopes.transferRequest();
            return data;
          }
        };

    ImmutableMap<Key<?>, Object> seedMap = ImmutableMap.of();
    Data data = ServletScopes.scopeRequest(callable, seedMap).call();

    ExecutorService executor = Executors.newSingleThreadExecutor();
    RequestScoper.CloseableScope scope = data.scoper.open();
    try {
      assertSame(data.object, injector.getInstance(Object.class));
    } finally {
      scope.close();
      executor.shutdownNow();
    }
  }

  public void testTransferNonHttpRequest_concurrentUseBlocks() throws Exception {
    Callable<Boolean> callable =
        new Callable<Boolean>() {
          @Override
          public Boolean call() throws Exception {
            ExecutorService executor = Executors.newSingleThreadExecutor();
            try {
              Future<Boolean> future =
                  executor.submit(ServletScopes.transferRequest(FALSE_CALLABLE));
              try {
                return future.get(100, TimeUnit.MILLISECONDS);
              } catch (TimeoutException e) {
                return true;
              }
            } finally {
              executor.shutdownNow();
            }
          }
        };

    ImmutableMap<Key<?>, Object> seedMap = ImmutableMap.of();
    assertTrue(ServletScopes.scopeRequest(callable, seedMap).call());
  }

  public void testTransferNonHttpRequest_concurrentUseBlocks_closeable() throws Exception {
    Callable<Boolean> callable =
        new Callable<Boolean>() {
          @Override
          public Boolean call() throws Exception {
            final RequestScoper scoper = ServletScopes.transferRequest();
            ExecutorService executor = Executors.newSingleThreadExecutor();
            try {
              Future<Boolean> future =
                  executor.submit(
                      new Callable<Boolean>() {
                        @Override
                        public Boolean call() {
                          RequestScoper.CloseableScope scope = scoper.open();
                          try {
                            return false;
                          } finally {
                            scope.close();
                          }
                        }
                      });
              try {
                return future.get(100, TimeUnit.MILLISECONDS);
              } catch (TimeoutException e) {
                return true;
              }
            } finally {
              executor.shutdownNow();
            }
          }
        };

    ImmutableMap<Key<?>, Object> seedMap = ImmutableMap.of();
    assertTrue(ServletScopes.scopeRequest(callable, seedMap).call());
  }

  public void testTransferNonHttpRequest_concurrentUseSameThreadOk() throws Exception {
    Callable<Boolean> callable =
        new Callable<Boolean>() {
          @Override
          public Boolean call() throws Exception {
            return ServletScopes.transferRequest(FALSE_CALLABLE).call();
          }
        };

    ImmutableMap<Key<?>, Object> seedMap = ImmutableMap.of();
    assertFalse(ServletScopes.scopeRequest(callable, seedMap).call());
  }

  public void testTransferNonHttpRequest_concurrentUseSameThreadOk_closeable() throws Exception {
    Callable<Boolean> callable =
        new Callable<Boolean>() {
          @Override
          public Boolean call() throws Exception {
            RequestScoper.CloseableScope scope = ServletScopes.transferRequest().open();
            try {
              return false;
            } finally {
              scope.close();
            }
          }
        };

    ImmutableMap<Key<?>, Object> seedMap = ImmutableMap.of();
    assertFalse(ServletScopes.scopeRequest(callable, seedMap).call());
  }
}
