package com.github.marschall.memoryfilesystem;

import java.io.IOException;
import java.nio.file.attribute.GroupPrincipal;
import java.util.concurrent.Callable;

/**
 * Provides access to the group of the current user.
 */
public final class CurrentGroup {

  private CurrentGroup() {
    throw new AssertionError("not instantiable");
  }

  private static final ThreadLocal<GroupPrincipal> GROUP = new ThreadLocal<>();

  /**
   * Sets the current group for a certain period.
   *
   * @param group the group to use
   * @param task during this task the given group will be used, will be called
   *  immediately by the current thread
   * @param <V> the type of the return value
   * @return what the task returned
   * @throws IOException if any of the code in the task throws an
   *  {@link IOException}
   */
  public static <V> V useDuring(GroupPrincipal group, GroupTask<V> task) throws IOException {
    GroupPrincipal previous = GROUP.get();
    try {
      GROUP.set(group);
      return task.call();
    } finally {
      if (previous == null) {
        GROUP.remove();
      } else {
        GROUP.set(previous);
      }
    }
  }

  /**
   * Sets the current group for a certain period.
   *
   * @param group the group to use
   * @param task during this task the given group will be used, will be called
   *  immediately by the current thread
   * @throws IOException if any of the code in the task throws an
   *  {@link IOException}
   *
   * @since 2.4.0
   */
  public static void useDuring(GroupPrincipal group, VoidGroupTask task) throws IOException {
    useDuring(group, () -> {
      task.call();
      return null;
    });
  }

  static GroupPrincipal get() {
    return GROUP.get();
  }

  /**
   * Functional interface for a task during which a certain group should be used.
   *
   * @param <V> the type of the return value
   */
  @FunctionalInterface
  public interface GroupTask<V> extends Callable<V> {

    /**
     * Executes the task.
     *
     * @return the return value of the task
     * @throws IOException if any of the code in the task throws an
     *  {@link IOException}
     */
    @Override
    V call() throws IOException;

  }

  /**
   * Functional interface for a task during which a certain group should be used.
   *
   * @since 2.4.0
   */
  @FunctionalInterface
  public interface VoidGroupTask {

    /**
     * Executes the task.
     *
     * @throws IOException if any of the code in the task throws an
     *  {@link IOException}
     */
    void call() throws IOException;

  }

}
