// Copyright 2011 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.util;

import static com.google.testing.junit.runner.util.TestPropertyRunnerIntegration.getCallbackForThread;

import java.util.HashMap;
import java.util.Map;

/**
 * Exports test properties to the test XML.
 */
public class TestPropertyExporter {
  /*
   * The global {@code TestPropertyExporter}, which writes the properties into
   * the test XML if the test is running from the command line.<p>
   *
   * If you have test infrastructure that needs to export properties, consider
   * injecting an instance of {@code TestPropertyExporter}. Your tests can
   * use one of the static methods in this class to create a fake instance.
   */
  public static final TestPropertyExporter INSTANCE = new TestPropertyExporter(
      new DefaultCallback());

  // Set to 1000 so that it will play nice with code that doesn't use exportRepeatedProperty
  // yet.
  public static final int INITIAL_INDEX_FOR_REPEATED_PROPERTY = 1000;

  private final Callback callback;

  /**
   * Creates a fake {@code TestPropertyExporter} instance, storing values
   * in the passed-in map.
   *
   * @param backingMap Map to use to store values
   * @return exporter instance
   */
  public static TestPropertyExporter createFake(final Map<String, String> backingMap) {
    return createFake(new Callback() {

      private final Map<String, Integer> repeatedPropertyNamesToRepetitions = new HashMap<>();

      @Override public void exportProperty(String name, String value) {
        backingMap.put(name, value);
      }

      @Override
      public String exportRepeatedProperty(String name, String value) {
        String propertyName = getRepeatedPropertyName(name);
        backingMap.put(propertyName, value);
        return propertyName;
      }

      private String getRepeatedPropertyName(String name) {
        int index = addNameToRepeatedPropertyNamesAndGetRepetitionsNr(name)
            + INITIAL_INDEX_FOR_REPEATED_PROPERTY;
        return name + index;
      }

      private synchronized int addNameToRepeatedPropertyNamesAndGetRepetitionsNr(String name) {
        Integer previousRepetitionsNr = repeatedPropertyNamesToRepetitions.get(name);
        if (previousRepetitionsNr == null) {
          previousRepetitionsNr = 0;
        }
        repeatedPropertyNamesToRepetitions.put(name, previousRepetitionsNr + 1);
        return previousRepetitionsNr;
      }
    });
  }

  /**
   * Creates a fake {@code TestPropertyExporter} instance, passing values
   * to the passed-in callback.
   *
   * @param callback Callback to use when values are exported
   * @return exporter instance
   */
  public static TestPropertyExporter createFake(final Callback callback) {
    return new TestPropertyExporter(callback);
  }

  protected TestPropertyExporter(Callback callback) {
    this.callback = callback;
  }

  /**
   * Exports a property to the test runner. This method is a no-op unless called
   * by the thread running the current test.
   *
   * @param name The property name.
   * @param value The property value.
   * @throws IllegalArgumentException if the name is not a valid name
   */
  public void exportProperty(String name, String value) {
    callback.exportProperty(name, value);
  }

  /**
   * Exports a property to the test runner by adding the value to the list of values for the
   * given property name.
   * When the properties get written to the XML, each name will have a numeric value appended to it
   * that is guaranteed to be unique for the given test case.
   * This method is a no-op unless called by the thread running the current test.
   *
   * @param name The property name.
   * @param value The property value.
   * @return the name of the property that was exported
   * @throws IllegalArgumentException if the name is not a valid name
   */
  public String exportRepeatedProperty(String name, String value) {
    return callback.exportRepeatedProperty(name, value);
  }

  /**
   * Callback that is used to store test properties.
   */
  public interface Callback {

    /**
     * Export the property.
     *
     * @param name The property name.
     * @param value The property value.
     */
    void exportProperty(String name, String value);

    /**
     * Export the property with an incrementing numeric suffix.
     *
     * @param name The property name.
     * @param value The property value.
     * @return the name of the property that was exported
     */
    String exportRepeatedProperty(String name, String value);
  }


  /**
   * Default callback implementation.
   * Calls the test runner to write the property to the XML.
   */
  private static class DefaultCallback implements Callback {

    @Override
    public void exportProperty(String name, String value) {
      getCallbackForThread().exportProperty(name, value);
    }

    @Override
    public String exportRepeatedProperty(String name, String value) {
      return getCallbackForThread().exportRepeatedProperty(name, value);
    }
  }
}
