/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.smartdata.erasurecode.test;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
 * Helper class to create one-liner stubs, so that instead of: <pre>
 * SomeType someDescriptiveMock = mock(SomeType.class);
 * when(someDescriptiveMock.someMethod()).thenReturn(someValue);</pre>
 * <p>You can now do: <pre>
 * SomeType someDescriptiveMock = make(stub(SomeType.class)
 *     .returning(someValue).from.someMethod());</pre>
 */
public class MockitoMaker {

  /**
   * Create a mock object from a mocked method call.
   *
   * @param <T> type of mocked object
   * @param methodCall  for mocked object
   * @return mocked object
   */
  @SuppressWarnings("unchecked")
  public static <T> T make(Object methodCall) {
    StubBuilder<T> sb = StubBuilder.current();
    when(methodCall).thenReturn(sb.firstReturn, sb.laterReturns);
    return (T) StubBuilder.current().from;
  }

  /**
   * Create a stub builder of a mocked object.
   *
   * @param <T>     type of the target object to be mocked
   * @param target  class of the target object to be mocked
   * @return the stub builder of the mocked object
   */
  public static <T> StubBuilder<T> stub(Class<T> target) {
    return new StubBuilder<T>(mock(target));
  }

  /**
   * Builder class for stubs
   * @param <T> type of the object to be mocked
   */
  public static class StubBuilder<T> {

    /**
     * The target mock object
     */
    public final T from;

    // We want to be able to use this even when the tests are run in parallel.
    @SuppressWarnings("rawtypes")
    private static final ThreadLocal<StubBuilder> tls =
        new ThreadLocal<StubBuilder>() {
          @Override protected StubBuilder initialValue() {
            return new StubBuilder();
          }
        };

    private Object firstReturn = null;
    private Object[] laterReturns = {};

    /**
     * Default constructor for the initial stub builder
     */
    public StubBuilder() {
      this.from = null;
    }

    /**
     * Construct a stub builder with a mock instance
     *
     * @param mockInstance  the mock object
     */
    public StubBuilder(T mockInstance) {
      tls.set(this);
      this.from = mockInstance;
    }

    /**
     * Get the current stub builder from thread local
     *
     * @param <T>
     * @return the stub builder of the mocked object
     */
    @SuppressWarnings("unchecked")
    public static <T> StubBuilder<T> current() {
      return tls.get();
    }

    /**
     * Set the return value for the current stub builder
     *
     * @param value the return value
     * @return the stub builder
     */
    public StubBuilder<T> returning(Object value) {
      this.firstReturn = value;
      return this;
    }

    /**
     * Set the return values for the current stub builder
     *
     * @param value   the first return value
     * @param values  the return values for later invocations
     * @return the stub builder
     */
    public StubBuilder<T> returning(Object value, Object... values) {
      this.firstReturn = value;
      this.laterReturns = values;
      return this;
    }
  }
}
