/**
 * Copyright (c) The openTCS Authors.
 *
 * This program is free software and subject to the MIT license. (For details,
 * see the licensing information (LICENSE.txt) you should have received with
 * this copy of the software.)
 */
package org.opentcs.configuration.cfg4j;

import java.util.ArrayList;
import java.util.List;
import static java.util.Objects.requireNonNull;
import org.cfg4j.source.ConfigurationSource;
import org.cfg4j.source.reload.ReloadStrategy;
import org.cfg4j.source.reload.Reloadable;
import static org.opentcs.util.Assertions.checkArgument;
import org.opentcs.util.CyclicTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A reload strategy for reloading {@link ConfigurationSource}s periodically.
 * Once a {@link ConfigurationSource} is registered to this strategy, a task is started reloading
 * all registered {@link ConfigurationSource}s periodically at the specified interval.
 * Once there are no more {@link ConfigurationSource}s registered, the task is terminated.
 */
public class PeriodicalReloadStrategy
    implements ReloadStrategy {

  /**
   * This class's logger.
   */
  private static final Logger LOG = LoggerFactory.getLogger(PeriodicalReloadStrategy.class);
  /**
   * The resources to reload.
   */
  private final List<Reloadable> resources = new ArrayList<>();
  /**
   * The task that's actually reloading the resources.
   */
  private final ReloadTask reloadTask;
  /**
   * The thread the {@link ReloadTask} runs in.
   */
  private Thread reloadThread;

  /**
   * Creates a new instance.
   *
   * @param duration The time (in ms) to wait between the reloads.
   */
  public PeriodicalReloadStrategy(long duration) {
    checkArgument(duration >= 0, "duration is %s, has to be => 0", duration);
    reloadTask = new ReloadTask(duration);
  }

  @Override
  public void register(Reloadable resource) {
    requireNonNull(resource, "resource");
    boolean emptyBefore = resources.isEmpty();

    if (resources.contains(resource)) {
      LOG.debug("Resource {} already registered.", resource);
      return;
    }

    LOG.debug("Registering resource {}.", resource);
    resources.add(resource);

    if (emptyBefore) {
      reloadThread = new Thread(reloadTask, getClass().getSimpleName() + "-reloadThread");
      reloadThread.setDaemon(true);
      reloadThread.start();
    }
  }

  @Override
  public void deregister(Reloadable resource) {
    if (resources.remove(resource)) {
      LOG.debug("Deregistered resource {}.", resource);
    }

    if (resources.isEmpty()) {
      reloadTask.terminateAndWait();
    }
  }

  /**
   * The task that's actually reloading the resources.
   */
  private class ReloadTask
      extends CyclicTask {

    ReloadTask(long tSleep) {
      super(tSleep);
    }

    @Override
    protected void runActualTask() {
      for (Reloadable resource : resources) {
        resource.reload();
      }
    }
  }
}
