// Copyright 2010 The Bazel Authors. All Rights Reserved.
//
// 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.testing.junit.runner.sharding;

import com.google.testing.junit.runner.sharding.api.ShardingFilterFactory;

import org.junit.runner.Description;
import org.junit.runner.manipulation.Filter;

import java.util.Collection;

import javax.inject.Inject;

/**
 * A factory for test sharding filters.
 */
public class ShardingFilters {

  /**
   * An enum of strategies for generating test sharding filters.
   */
  public static enum ShardingStrategy implements ShardingFilterFactory {

    /**
     * {@link com.google.testing.junit.runner.sharding.HashBackedShardingFilter}
     */
    HASH {
      @Override
      public Filter createFilter(Collection<Description> testDescriptions,
          int shardIndex, int totalShards) {
        return new HashBackedShardingFilter(shardIndex, totalShards);
      }
    },

    /**
     * {@link com.google.testing.junit.runner.sharding.RoundRobinShardingFilter}
     */
    ROUND_ROBIN {
      @Override
      public Filter createFilter(Collection<Description> testDescriptions,
          int shardIndex, int totalShards) {
        return new RoundRobinShardingFilter(testDescriptions, shardIndex, totalShards);
      }
    }
  }

  public static final ShardingFilterFactory DEFAULT_SHARDING_STRATEGY =
      ShardingStrategy.ROUND_ROBIN;
  private final ShardingEnvironment shardingEnvironment;
  private final ShardingFilterFactory defaultShardingStrategy;

  /**
   * Creates a factory with the given sharding environment and the
   * default sharding strategy.
   */
  public ShardingFilters(ShardingEnvironment shardingEnvironment) {
    this(shardingEnvironment, DEFAULT_SHARDING_STRATEGY);
  }

  /**
   * Creates a factory with the given sharding environment and sharding
   * strategy.
   */
  @Inject
  public ShardingFilters(ShardingEnvironment shardingEnvironment,
      ShardingFilterFactory defaultShardingStrategy) {
    this.shardingEnvironment = shardingEnvironment;
    this.defaultShardingStrategy = defaultShardingStrategy;
  }

  /**
   * Creates a sharding filter according to strategy specified by the
   * sharding environment.
   */
  public Filter createShardingFilter(Collection<Description> descriptions) {
    ShardingFilterFactory factory = getShardingFilterFactory();
    return factory.createFilter(descriptions, shardingEnvironment.getShardIndex(),
        shardingEnvironment.getTotalShards());
  }

  private ShardingFilterFactory getShardingFilterFactory() {
    String strategy = shardingEnvironment.getTestShardingStrategy();
    if (strategy == null) {
      return defaultShardingStrategy;
    }
    ShardingFilterFactory shardingFilterFactory;
    try {
      shardingFilterFactory = ShardingStrategy.valueOf(strategy.toUpperCase());
    } catch (IllegalArgumentException e) {
      try {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Class<? extends ShardingFilterFactory> strategyClass =
            classLoader.loadClass(strategy).asSubclass(ShardingFilterFactory.class);
        shardingFilterFactory = strategyClass.getConstructor().newInstance();
      } catch (ReflectiveOperationException | IllegalArgumentException e2) {
        throw new RuntimeException(
            "Could not create custom sharding strategy class " + strategy, e2);
      }
    }
    return shardingFilterFactory;
  }
}
