
package de.ivu.elect.jee.background;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

import javax.ejb.Asynchronous;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.jee.performance.PerformanceStore;
import de.ivu.elect.jee.performance.PhaseIdEnum;
import de.ivu.elect.jee.performance.ViewAccessTarget;
import de.ivu.elect.jee.performance.ViewAccessType;
import de.ivu.elect.jee.performance.ViewType;

// abgeleitete Klasse mit folgendem annotieren:
// @Singleton
// https://issues.jboss.org/browse/EJBTHREE-2070 (keine Transaktion)
// @TransactionManagement(TransactionManagementType.BEAN)
// wichtig, abgeleitete Klasse mit @EJB injizieren da mit @Inject die asynchrone Ausführung nicht funktioniert
public abstract class AbstractBackgroundService implements Runnable {

  public static final long RUNNING_INITIAL = 0L;

  public AbstractBackgroundService() {
    super();
  }

  private final Logger LOG = LoggerFactory.getLogger(getClass());

  protected final AtomicBoolean running = new AtomicBoolean(false);

  protected abstract AtomicLong getRunning();

  @Override
  @Asynchronous
  public void run() {
    try {
      do {
        if (isDisabled()) {
          return;
        }

        getRunning().set(RUNNING_INITIAL + 1); // Aktuell auf eine weitere Ausführung setzen
        boolean stopRunning;
        try {
          boolean didSomething;
          do {
            if (isDisabled()) {
              return;
            }

            didSomething = false;

            while (isSomethingToDoCheck()) {
              running.set(true);
              startAusfuehrung();
              didSomething = true;
              long start = System.currentTimeMillis();
              try {
                doSomething();
              } finally {
                loggeStatistiken(start, " - execution");
              }
              running.set(false);
            }
          } while (didSomething);
        } finally {
          running.set(false);
          endeAusfuehrung();
          stopRunning = getRunning().decrementAndGet() <= RUNNING_INITIAL; // Eine Ausführung reduzieren => sollte bei 0 sein
        }
        if (stopRunning) {
          return;
        }
      } while (true); // Falls nicht im finally Block beendet, nochmal ausführen, da noch was zu tun sein könnte...
    } finally {
      getRunning()
          .set(RUNNING_INITIAL); // ELECT-8258 beim Verlassen der run()-Methode garantieren, dass die startIfNotRunning()-Methode run() wieder aufrufen kann
    }
  }

  private void loggeStatistiken(long start, String aktion) {
    try {
      if (PerformanceStore.isStatistikAktiviert()) {
        PerformanceStore.addDuration(
            new ViewAccessTarget(ViewType.BACKGROUND, ViewAccessType.GESAMT, PhaseIdEnum.INVOKE_APPLICATION, ViewAccessTarget.SESSION_ID_OHNE,
                getClass().getSimpleName() + aktion), System.currentTimeMillis() - start);
      }
    } catch (Exception e) {
      LOG.warn("Fehler beim Loggen der Statistiken: " + e.getMessage() + ", " + e.getClass());
    }
  }

  private boolean isSomethingToDoCheck() {
    if (isDisabled()) {
      return false;
    }

    long start = System.currentTimeMillis();
    try {
      return isSomethingToDo();
    } finally {
      loggeStatistiken(start, " - todo check");
    }

  }

  protected boolean isRunning() {
    return running.get();
  }

  public static AtomicLong createRunning() {
    return new AtomicLong(RUNNING_INITIAL);
  }

  // statische Methode, da @Singleton
  public static void startIfNotRunning(AbstractBackgroundService service, AtomicLong running) {
    if (running.getAndIncrement() == RUNNING_INITIAL) { // Immer um eine Ausführung erhöhen, falls vorher 0
      service.run(); // Ausführung aufrufen
    }
  }

  protected void endeAusfuehrung() {
    // nichts zu tun
  }

  protected void startAusfuehrung() {
    // nichts zu tun
  }

  protected abstract boolean isSomethingToDo();

  protected abstract void doSomething();

  protected abstract boolean isDisabled();
}
