
package de.ivu.elect.business.exchange.entity;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class ExchangeQueue implements Serializable {

  private static final long serialVersionUID = 1L;

  private final Map<ExchangeQueueType, Queue<ExchangeQueueJob>> jobQueue;

  private final Map<ExchangeQueueType, Set<ExchangeQueueJob>> runningJobs;

  {
    jobQueue = new HashMap<>(); // keine ConcurrentMap notwendig, wird nicht verändert...
    runningJobs = new HashMap<>();// keine ConcurrentMap notwendig, wird nicht verändert...

    for (ExchangeQueueType exchangeQueueType : ExchangeQueueType.values()) {
      jobQueue.put(exchangeQueueType, new ConcurrentLinkedQueue<ExchangeQueueJob>());
      runningJobs.put(exchangeQueueType, Collections.newSetFromMap(new ConcurrentHashMap()));
    }
  }

  public ExchangeQueueToken createJob(String user, ExchangeQueueType queueType, ExchangeQueueJobWeight jobWeight, ExchangeQueueJobPriority jobPriority) {
    ExchangeQueueJob job = new ExchangeQueueJob(user, queueType, jobWeight, jobPriority);
    jobQueue.get(job.getQueueType()).add(job);
    return new ExchangeQueueToken(this, job);
  }

  void finish(ExchangeQueueJob job) {
    if (job != null) {
      ExchangeQueueType type = job.getQueueType();

      Collection<ExchangeQueueJob> jobsRunning = runningJobs.get(type);

      if (jobsRunning != null) {
        jobsRunning.remove(job);
      }

      Collection<ExchangeQueueJob> jobsQueued = jobQueue.get(type);

      if (jobsQueued != null) {
        jobsQueued.remove(job);
      }
    }
  }

  public ExchangeQueueStatus getStatus(ExchangeQueueJob job) {

    ConcurrentLinkedQueue<ExchangeQueueJob> myJobQueue = new ConcurrentLinkedQueue<>(jobQueue.get(job.getQueueType()));
    HashSet<ExchangeQueueJob> myRunningJobs = new HashSet<>(runningJobs.get(job.getQueueType()));

    Long anzahlVorhandenerSlots = getNumberOfSlots(job.getQueueType());
    Long anzahlBelegterSlots = bestimmeAnzahlBelgterSlost(myRunningJobs);

    boolean jobLaeuft = myRunningJobs.contains(job);
    boolean jobKannLaufen = jobLaeuft || jobKannStarten(anzahlVorhandenerSlots, anzahlBelegterSlots, job, myJobQueue);

    if (!jobLaeuft && jobKannLaufen) {
      boolean started = starteJob(job);
    }

    List<ExchangeQueueJob> laufendeJobs = new ArrayList<>(myRunningJobs);

    //    Long laengeWarteschlange;
    //    Long positionWarteschlange;
    //    return new ExchangeQueueStatus(jobKannLaufen, anzahlVorhandenerSlots, laufendeJobs, laengeWarteschlange, positionWarteschlange);
    return null;

  }

  // double check
  private synchronized boolean starteJob(ExchangeQueueJob job) {
    Long anzahlVorhandenerSlots = getNumberOfSlots(job.getQueueType());
    Set<ExchangeQueueJob> myRunningJobs = runningJobs.get(job.getQueueType());
    Queue<ExchangeQueueJob> myJobQueue = jobQueue.get(job.getQueueType());
    Long anzahlBelegterSlots = bestimmeAnzahlBelgterSlost(myRunningJobs);

    boolean jobLaeuft = myRunningJobs.contains(job);
    boolean jobKannLaufen = jobLaeuft || jobKannStarten(anzahlVorhandenerSlots, anzahlBelegterSlots, job, myJobQueue);

    if (!jobLaeuft && jobKannLaufen) {
      myJobQueue.remove(job);
      myRunningJobs.add(job);
    }

    return jobKannLaufen;

  }

  private boolean jobKannStarten(Long anzahlVorhandenerSlots, Long anzahlBelegterSlots, ExchangeQueueJob job, Queue<ExchangeQueueJob> jobQueue) {
    long anzahlFreierSlots = anzahlVorhandenerSlots - anzahlBelegterSlots;

    for (ExchangeQueueJob exchangeQueueJob : jobQueue) {
      anzahlFreierSlots -= exchangeQueueJob.getJobWeight().getAnzahlBenoetigterSlots();
      if (exchangeQueueJob.equals(job)) {
        return anzahlFreierSlots >= 0;
      }
      if (anzahlFreierSlots <= 0) {
        return false;
      }
    }
    return false;
  }

  private Long bestimmeAnzahlBelgterSlost(Set<ExchangeQueueJob> myRunningJobs) {
    long result = 0;
    for (ExchangeQueueJob myRunningJob : myRunningJobs) {
      result += myRunningJob.getJobWeight().getAnzahlBenoetigterSlots();
    }

    return result;
  }

  protected Long getNumberOfSlots(ExchangeQueueType queueType) {
    return 2L;
  }
}
