package de.ivu.elect.business.dokumente.control;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.ejb.Stateless;
import javax.enterprise.inject.Specializes;
import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.document.generator.template.OpenOfficeKonfiguration;
import de.ivu.elect.business.ElectSystemProperties;
import de.ivu.elect.business.konfiguration.entity.KonfigurationLoader;


import static de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte.OPEN_OFFICE_ADDITIONAL_PORT_NUMBERS;
import static de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte.OPEN_OFFICE_DEFAULT_PORT_NUMBER;
import static de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte.OPEN_OFFICE_DISABLE_OPENGL;
import static de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte.OPEN_OFFICE_FAIL_FAST;
import static de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte.OPEN_OFFICE_KILL_EXISTING_PROCESS;
import static de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte.OPEN_OFFICE_MAX_RETRIES_CANCELLATION;
import static de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte.OPEN_OFFICE_MAX_TASKS_PER_PROCESS;
import static de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte.OPEN_OFFICE_NUMBER_OF_ADDITIONAL_PORT_NUMBERS;
import static de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte.OPEN_OFFICE_PATH;
import static de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte.OPEN_OFFICE_PROBLEM_CHANGE_PORTNUMBER;
import static de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte.OPEN_OFFICE_PROCESS_RETRY_INTERVAL_IN_MS;
import static de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte.OPEN_OFFICE_PROCESS_TIMEOUT_IN_MS;
import static de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte.OPEN_OFFICE_TASK_EXECUTION_TIMEOUT_IN_MS;
import static de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte.OPEN_OFFICE_TASK_QUEUE_TIMEOUT_IN_MS;
import static de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte.OPEN_OFFICE_TEMPLATE_PROFILE_DIR;
import static de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte.OPEN_OFFICE_WORKING_DIR;

@Stateless
@Specializes
public class OpenOfficeKonfigurationFactory extends BaseOpenOfficeKonfigurationFactory {

  private static final long serialVersionUID = 1L;

  private static final Logger LOG = LoggerFactory.getLogger(OpenOfficeKonfigurationFactory.class);

  public static final AtomicInteger PORT_OFFSET = new AtomicInteger(0); // ELECT-15923

  private static final String ANZAHL_EXTRA_PORTNUMBER_OVERRIDE = System.getProperty(ElectSystemProperties.ELECT_LIBRE_OFFICE_EXTRA_PORTNUMBER);

  @Inject
  private transient OpenOfficeApplication openOfficeApplication;

  @Inject
  private transient KonfigurationLoader konfigurationLoader;

  public OpenOfficeKonfiguration createConfig() {
    openOfficeApplication.foo();
    String openOfficePath = emptyToNull(konfigurationLoader.resolveStringValue(OPEN_OFFICE_PATH));
    int[] portNumbers = bestimmePortnummern();
    String workingDir = emptyToNull(konfigurationLoader.resolveStringValue(OPEN_OFFICE_WORKING_DIR));
    boolean killExistingProcess = konfigurationLoader.resolveBooleanValue(OPEN_OFFICE_KILL_EXISTING_PROCESS);
    boolean disableOpengl = konfigurationLoader.resolveBooleanValue(OPEN_OFFICE_DISABLE_OPENGL);
    boolean failFast = konfigurationLoader.resolveBooleanValue(OPEN_OFFICE_FAIL_FAST);
    long processTimeoutInMs = getProcessTimeoutInMs();
    long processRetryIntervalnMs = getProcessRetryIntervalnMs();
    long taskExecutionTimeoutInMs = getTaskExecutionTimeoutInMs();
    long taskQueueTimeoutInMs = getTaskQueueTimeoutInMs();
    int maxTasksPerProcess = getMaxTasksPerProcess();
    int maxRetriesCancellation = getMaxRetriesCancellation();
    String templateProfileDir = emptyToNull(konfigurationLoader.resolveStringValue(OPEN_OFFICE_TEMPLATE_PROFILE_DIR));

    OpenOfficeKonfiguration openOfficeKonfiguration = new OpenOfficeKonfiguration() {
      @Nullable
      @Override
      public String getOpenOfficePath() {
        return openOfficePath;
      }

      @Override
      public int[] getOpenOfficePortNumbers() {
        return portNumbers;
      }

      @Nullable
      @Override
      public String getWorkingDir() {
        return workingDir;
      }

      @Override
      public boolean killExistingProcess() {
        return killExistingProcess;
      }

      @Override
      public boolean disableOpengl() {
        return disableOpengl;
      }

      @Override
      public long getProcessTimeoutInMs() {
        return processTimeoutInMs;
      }

      @Override
      public long getProcessRetryIntervalnMs() {
        return processRetryIntervalnMs;
      }

      @Override
      public long getTaskExecutionTimeoutInMs() {
        return taskExecutionTimeoutInMs;
      }

      @Override
      public int getMaxTasksPerProcess() {
        return maxTasksPerProcess;
      }

      @Nullable
      @Override
      public String getTemplateProfileDir() {
        return templateProfileDir;
      }

      @Override
      public long getTaskQueueTimeoutInMs() {
        return taskQueueTimeoutInMs;
      }

      @Override
      public long getAnzahlRetries() {
        return maxRetriesCancellation;
      }

      @Override
      public boolean isFailFast() {
        return failFast;
      }

      @Override
      public String toString() {
        return "OpenOfficeKonfiguration{" + "openOfficePath='" + getOpenOfficePath() + '\'' + ", openOfficePortNumbers=" + Arrays.toString(
            getOpenOfficePortNumbers()) + ", workingDir='" + getWorkingDir() + '\'' + ", killExistingProcess=" + killExistingProcess() + ", disableOpengl=" + disableOpengl() + ", processTimeoutInMs=" + getProcessTimeoutInMs() + ", processRetryIntervalnMs=" + getProcessRetryIntervalnMs() + ", taskExecutionTimeoutInMs=" + getTaskExecutionTimeoutInMs() + ", maxTasksPerProcess=" + getMaxTasksPerProcess() + ", templateProfileDir='" + getTemplateProfileDir() + '\'' + ", taskQueueTimeoutInMs=" + getTaskQueueTimeoutInMs() + ", anzahlRetries=" + getAnzahlRetries() + '}';
      }
    };
    // System.out.println("openOfficeKonfiguration = " + openOfficeKonfiguration);
    return openOfficeKonfiguration;
  }

  protected int getMaxRetriesCancellation() {
    return konfigurationLoader.resolveIntegerValue(OPEN_OFFICE_MAX_RETRIES_CANCELLATION);
  }

  protected long getTaskQueueTimeoutInMs() {
    return konfigurationLoader.resolveLongValue(OPEN_OFFICE_TASK_QUEUE_TIMEOUT_IN_MS);
  }

  protected long getTaskExecutionTimeoutInMs() {
    return konfigurationLoader.resolveLongValue(OPEN_OFFICE_TASK_EXECUTION_TIMEOUT_IN_MS);
  }

  protected long getProcessRetryIntervalnMs() {
    return konfigurationLoader.resolveLongValue(OPEN_OFFICE_PROCESS_RETRY_INTERVAL_IN_MS);
  }

  protected long getProcessTimeoutInMs() {
    return konfigurationLoader.resolveLongValue(OPEN_OFFICE_PROCESS_TIMEOUT_IN_MS);
  }

  protected int getMaxTasksPerProcess() {
    return konfigurationLoader.resolveIntegerValue(OPEN_OFFICE_MAX_TASKS_PER_PROCESS);
  }

  private String emptyToNull(String s) {
    if (StringUtils.isBlank(s)) {
      return null;
    }
    return s;
  }

  private int[] bestimmePortnummern() {

    var portNumber = konfigurationLoader.resolveIntegerValue(OPEN_OFFICE_DEFAULT_PORT_NUMBER) + PORT_OFFSET.get();

    if (portNumber > 65535) {
      portNumber %= 65535;
    }

    List<Integer> nrList = new ArrayList<>();
    nrList.add(portNumber);

    var additonal = konfigurationLoader.resolveStringValue(OPEN_OFFICE_ADDITIONAL_PORT_NUMBERS);

    if (StringUtils.isNotBlank(additonal)) {
      nrList.addAll(Arrays.stream(additonal.split(",")).filter(Objects::nonNull).map(String::trim).map(Integer::parseInt).collect(Collectors.toList()));
    } else {
      var anzahlExtraPortnummern = getAnzahlExtraPortnummern();
      for (var i = 0; i < anzahlExtraPortnummern; i++) {
        nrList.add(portNumber + i);
      }
    }

    return nrList.stream().mapToInt(Integer::intValue).toArray();
  }

  protected long getAnzahlExtraPortnummern() {
    // ELECT-16161
    if (StringUtils.isNotBlank(ANZAHL_EXTRA_PORTNUMBER_OVERRIDE)) {
      return Long.parseLong(ANZAHL_EXTRA_PORTNUMBER_OVERRIDE);
    }
    return konfigurationLoader.resolveLongValue(OPEN_OFFICE_NUMBER_OF_ADDITIONAL_PORT_NUMBERS);
  }

  public void handleError() {
    if (konfigurationLoader.resolveBooleanValue(OPEN_OFFICE_PROBLEM_CHANGE_PORTNUMBER)) {
      PORT_OFFSET.addAndGet(1);
      if (PORT_OFFSET.get() > 10000) {
        PORT_OFFSET.set(0);
      }
      LOG.info("Neuer Libre Office Port-Offset: " + PORT_OFFSET.get());
    }
  }

}
