/************************************************************************
    MeOS - Orienteering Software
    Copyright (C) 2009-2024 Melin Software HB

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License fro more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    Melin Software HB - software@melin.nu - www.melin.nu
    Eksoppsvägen 16, SE-75646 UPPSALA, Sweden

************************************************************************/

#include "stdafx.h"

#include <vector>
#include <deque>
#include <set>
#include <cassert>
#include <algorithm>
#include <chrono>
#include <random>

#include "oEvent.h"
#include "gdioutput.h"
#include "oDataContainer.h"

#include "random.h"

#include "meos.h"
#include "meos_util.h"
#include "localizer.h"
#include "gdifonts.h"
#include "oEventDraw.h"
#include "meosexception.h"

int ClassInfo::sSortOrder=0;

DrawInfo::DrawInfo() {
  changedVacancyInfo = true;
  changedExtraInfo = true;
  vacancyFactor = 0.05;
  extraFactor = 0.1;
  minVacancy = 1;
  maxVacancy = 10;
  baseInterval = timeConstMinute;
  minClassInterval = 2*timeConstMinute;
  maxClassInterval = 3*timeConstMinute;
  nFields = 10;
  firstStart = timeConstHour;
  maxCommonControl = 3;
  allowNeighbourSameCourse = true;
  coursesTogether = false;
  // Statistics output from optimize start order
  numDistinctInit = -1;
  numRunnerSameInitMax = -1;
  minimalStartDepth = -1;
}

bool ClassInfo::operator <(ClassInfo &ci)
{
  if (sSortOrder==0) {
    return sortFactor > ci.sortFactor;
  }
  else if (sSortOrder == 2) {
    return pc->getSortIndex() < ci.pc->getSortIndex();
  }
  else if (sSortOrder == 3) {
    if (unique != ci.unique) {
      if (ci.nRunnersGroup != nRunnersGroup)
        return nRunnersGroup > ci.nRunnersGroup;
      else
        return unique < ci.unique;
    }
    else
      return firstStart<ci.firstStart;
  }
  else
    return firstStart<ci.firstStart;

}

struct ClassBlockInfo{
  int FirstControl;
  int nRunners;
  int Depth;

  int FirstStart;

  bool operator<(ClassBlockInfo &ci);
};

bool ClassBlockInfo::operator <(ClassBlockInfo &ci)
{
  return Depth<ci.Depth;
}

namespace {

  void getLargestClub(map<int, vector<pRunner> > &clubRunner, vector<pRunner> &largest)
  {
    size_t maxClub = 0;
    for (map<int, vector<pRunner> >::iterator it =
         clubRunner.begin(); it != clubRunner.end(); ++it) {
      maxClub = max(maxClub, it->second.size());
    }

    for (map<int, vector<pRunner> >::iterator it =
         clubRunner.begin(); it != clubRunner.end(); ++it) {
      if (it->second.size() == maxClub) {
        swap(largest, it->second);
        clubRunner.erase(it);
        return;
      }
    }
  }

  void getRange(int size, vector<int> &p) {
    p.resize(size);
    for (size_t k = 0; k < p.size(); k++)
      p[k] = k;
  }

  void drawSOFTMethod(vector<pRunner> &runners, bool handleBlanks) {
    if (runners.empty())
      return;

    //Group runners per club
    map<int, vector<pRunner> > clubRunner;

    for (size_t k = 0; k < runners.size(); k++) {
      int clubId = runners[k] ? runners[k]->getClubId() : -1;
      clubRunner[clubId].push_back(runners[k]);
    }

    vector< vector<pRunner> > runnerGroups(1);

    // Find largest club
    getLargestClub(clubRunner, runnerGroups[0]);

    int largeSize = runnerGroups[0].size();
    int ngroups = (runners.size() + largeSize - 1) / largeSize;
    runnerGroups.resize(ngroups);

    while (!clubRunner.empty()) {
      // Find the smallest available group
      unsigned small = runners.size() + 1;
      int cgroup = -1;
      for (size_t k = 1; k < runnerGroups.size(); k++)
        if (runnerGroups[k].size() < small) {
          cgroup = k;
          small = runnerGroups[k].size();
        }

      // Add the largest remaining group to the smallest.
      vector<pRunner> largest;
      getLargestClub(clubRunner, largest);
      runnerGroups[cgroup].insert(runnerGroups[cgroup].end(), largest.begin(), largest.end());
    }

    unsigned maxGroup = runnerGroups[0].size();

    //Permute the first group
    vector<int> pg(maxGroup);
    getRange(pg.size(), pg);
    permute(pg);
    vector<pRunner> pr(maxGroup);
    for (unsigned k = 0; k < maxGroup; k++)
      pr[k] = runnerGroups[0][pg[k]];
    runnerGroups[0] = pr;

    //Find the largest group
    for (size_t k = 1; k < runnerGroups.size(); k++)
      maxGroup = max<int>(maxGroup, runnerGroups[k].size());

    if (handleBlanks) {
      //Give all groups same size (fill with 0)
      for (size_t k = 1; k < runnerGroups.size(); k++)
        runnerGroups[k].resize(maxGroup);
    }

    // Apply algorithm recursivly to groups with several clubs
    for (size_t k = 1; k < runnerGroups.size(); k++)
      drawSOFTMethod(runnerGroups[k], true);

    // Permute the order of groups
    vector<int> p(runnerGroups.size());
    getRange(p.size(), p);
    permute(p);

    // Write back result
    int index = 0;
    for (unsigned level = 0; level < maxGroup; level++) {
      for (size_t k = 0; k < runnerGroups.size(); k++) {
        int gi = p[k];
        if (level < runnerGroups[gi].size() && (runnerGroups[gi][level] != 0 || !handleBlanks))
          runners[index++] = runnerGroups[gi][level];
      }
    }

    if (handleBlanks)
      runners.resize(index);
  }

  void drawMeOSMethod(vector<pRunner> &runners) {
    if (runners.empty())
      return;

    map<int, vector<pRunner>> runnersPerClub;
    for (pRunner r : runners)
      runnersPerClub[r->getClubId()].push_back(r);
    vector<pair<int, int>> sizeClub;
    for (auto &rc : runnersPerClub)
      sizeClub.emplace_back(rc.second.size(), rc.first);

    sort(sizeClub.rbegin(), sizeClub.rend());

    int targetGroupSize = max<int>(runners.size()/20, sizeClub.front().first);

    vector<vector<pRunner>> groups(1);
    for (auto &sc : sizeClub) {
      int currentSize = groups.back().size();
      int newSize = currentSize + sc.first;
      if (abs(currentSize - targetGroupSize) < abs(newSize - targetGroupSize)) {
        groups.emplace_back();
      }
      groups.back().insert(groups.back().end(), runnersPerClub[sc.second].begin(), 
                                                runnersPerClub[sc.second].end());
    }

    size_t nRunnerTot = runners.size();

    if (groups.front().size() > (nRunnerTot + 2) / 2 && groups.size() > 1) {
      // We cannot distribute without clashes -> move some to other groups to prevent tail of same club
      int toMove = groups.front().size() - (nRunnerTot + 2) / 2;
      for (int i = 0; i < toMove; i++) {
        int dest = 1 + i % (groups.size() - 1);
        groups[dest].push_back(groups.front().back());
        groups.front().pop_back();
      }
    }

    // Permute groups
    size_t maxGroupSize = 0;
    vector<int> pv;

    for (auto &group : groups) {
      pv.clear();
      for (size_t i = 0; i < group.size(); i++) {
        pv.push_back(i);
      }
      permute(pv);
      vector<pRunner> tg;
      tg.reserve(group.size());
      for (int i : pv)
        tg.push_back(group[i]);
      tg.swap(group);
      maxGroupSize = max(maxGroupSize, group.size());

    }

    runners.clear();
    size_t takeMaxGroupInterval;
    if (groups.size() > 10)
      takeMaxGroupInterval = groups.size() / 4;
    else
      takeMaxGroupInterval = max<int>(2u, groups.size() - 2);

    deque<int> recentGroups;

    int ix = 0;

    if (maxGroupSize * 2 > nRunnerTot) {
      takeMaxGroupInterval = 2;
      ix = 1;
    }

    while (true) {
      ix++;
      pair<size_t, int> currentMaxGroup(0, -1);
      int otherNonEmpty = -1;
      size_t nonEmptyGroups = 0;
      for (size_t gx = 0; gx < groups.size(); gx++) {
        if (groups[gx].empty())
          continue;
        
        nonEmptyGroups++;

        if (groups[gx].size() > currentMaxGroup.first) {
          if (otherNonEmpty == -1 || groups[otherNonEmpty].size() < currentMaxGroup.first)
            otherNonEmpty = currentMaxGroup.second;

          currentMaxGroup.first = groups[gx].size();
          currentMaxGroup.second = gx;
        }
        else {
          if (otherNonEmpty == -1 || groups[otherNonEmpty].size() < groups[gx].size())
            otherNonEmpty = gx;
        }
      }
      if (currentMaxGroup.first == 0)
        break; // Done
      
      int groupToUse = currentMaxGroup.second;
      if (ix != takeMaxGroupInterval) {
        // Select some other group
        for (size_t attempt = 0; attempt < groups.size() * 2; attempt++) {
          int g = GetRandomNumber(groups.size());
          if (!groups[g].empty() && count(recentGroups.begin(), recentGroups.end(), g) == 0) {
            groupToUse = g;
            break;
          }
        }
      }
      else {
        ix = 0;
      }

      if (!recentGroups.empty()) { //Make sure to avoid duplicates of same group (if possible)
        if (recentGroups.back() == groupToUse && otherNonEmpty != -1)
          groupToUse = otherNonEmpty;
      }

      // Try to spread groups by ensuring that the same group is not used near itself
      recentGroups.push_back(groupToUse);
      if (recentGroups.size() > takeMaxGroupInterval || recentGroups.size() >= nonEmptyGroups)
        recentGroups.pop_front();

      runners.push_back(groups[groupToUse].back());
      groups[groupToUse].pop_back();
    }
  }

  bool isFree(const DrawInfo &di, vector< vector<pair<int, int> > > &StartField, int nFields,
              int FirstPos, int PosInterval, ClassInfo &cInfo)
  {
    int Type = cInfo.unique;
    int courseId = cInfo.courseId;

    int nEntries = cInfo.nRunners;
    bool disallowNeighbors = !di.allowNeighbourSameCourse;
    // Adjust first pos to make room for extra (before first start)
    if (cInfo.nExtra > 0) {
      int newFirstPos = FirstPos - cInfo.nExtra * PosInterval;
      while (newFirstPos < 0)
        newFirstPos += PosInterval;
      int extra = (FirstPos - newFirstPos) / PosInterval;
      nEntries += extra;
      FirstPos = newFirstPos;
    }

    //Check if free at all...
    for (int k = 0; k < nEntries; k++) {
      bool hasFree = false;
      for (int f = 0; f < nFields; f++) {
        size_t ix = FirstPos + k * PosInterval;
        int t = StartField[f][ix].first;

        if (disallowNeighbors) {
          int prevT = -1, nextT = -1;
          if (PosInterval > 1 && ix + 1 < StartField[f].size())
            nextT = StartField[f][ix + 1].second;
          if (PosInterval > 1 && ix > 0)
            prevT = StartField[f][ix - 1].second;

          if ((nextT > 0 && nextT == courseId) || (prevT > 0 && prevT == courseId))
            return false;
        }
        if (t == 0)
          hasFree = true;
        else if (t == Type)
          return false;//Type of course occupied. Cannot put it here;
      }

      if (!hasFree) return false;//No free start position.
    }

    return true;
  }

  bool insertStart(vector< vector< pair<int, int> > > &StartField, int nFields, ClassInfo &cInfo)
  {
    int Type = cInfo.unique;
    int courseId = cInfo.courseId;
    int nEntries = cInfo.nRunners;
    int FirstPos = cInfo.firstStart;
    int PosInterval = cInfo.interval;

    // Adjust first pos to make room for extra (before first start)
    if (cInfo.nExtra > 0) {
      int newFirstPos = FirstPos - cInfo.nExtra * PosInterval;
      while (newFirstPos < 0)
        newFirstPos += PosInterval;
      int extra = (FirstPos - newFirstPos) / PosInterval;
      nEntries += extra;
      FirstPos = newFirstPos;
    }

    for (int k = 0; k < nEntries; k++) {
      bool HasFree = false;

      for (int f = 0; f < nFields && !HasFree; f++) {
        if (StartField[f][FirstPos + k * PosInterval].first == 0) {
          StartField[f][FirstPos + k * PosInterval].first = Type;
          StartField[f][FirstPos + k * PosInterval].second = courseId;
          HasFree = true;
        }
      }

      if (!HasFree)
        return false; //No free start position. Fail.
    }

    return true;
  }
}

class DrawOptimAlgo {
private:
  oEvent * oe;
  vector<pClass> Classes;
  vector<pRunner> Runners;

  int maxNRunner = 0;
  int maxGroup = 0;
  int maxCourse = 0;
  int bestEndPos = 0;
  
  map<int, ClassInfo> otherClasses;

  int bestEndPosGlobal = 0;

  static int optimalLayout(int interval, vector< pair<int, int> > &classes) {
    sort(classes.begin(), classes.end());

    vector<int> chaining(interval, 0);

    for (int k = int(classes.size()) - 1; k >= 0; k--) {
      int ix = 0;
      // Find free position
      for (int i = 1; i<interval; i++) {
        if (chaining[i] < chaining[ix])
          ix = i;
      }
      int nr = classes[k].first;
      if (chaining[ix] > 0)
        nr += classes[k].second;

      chaining[ix] += 1 + interval * (nr - 1);
    }

    int last = chaining[0];
    for (int i = 1; i<interval; i++) {
      last = max(chaining[i], last);
    }
    return last;
  }


  void computeBestStartDepth(DrawInfo &di, vector<ClassInfo> &cInfo, int useNControls, int alteration) {
    //OutputDebugString((L"Use NC:" + itow(useNControls)).c_str());
    if (di.firstStart <= 0)
      di.firstStart = 0;

    otherClasses.clear();
    cInfo.clear();
    map<int, int> runnerPerGroup;
    map<int, int> runnerPerCourse;
    int nRunnersTot = 0;
    for (auto c_it : Classes) {
      bool drawClass = di.classes.count(c_it->getId()) > 0;
      ClassInfo *cPtr = 0;

      if (!drawClass) {
        otherClasses[c_it->getId()] = ClassInfo(&*c_it);
        cPtr = &otherClasses[c_it->getId()];
      }
      else
        cPtr = &di.classes[c_it->getId()];

      ClassInfo &ci = *cPtr;
      pCourse pc = c_it->getCourse();

      if (pc && useNControls < 1000) {
        if (useNControls > 0 && pc->getNumControls() > 0)
          ci.unique = 1000000 + pc->getIdSum(useNControls);
        else
          ci.unique = 10000 + pc->getId();

        ci.courseId = pc->getId();
      }
      else
        ci.unique = ci.classId;

      if (!drawClass)
        continue;

      int nr = 0;
      if (ci.startGroupId == 0)
        nr = c_it->getNumRunners(true, true, true);
      else {
        vector<pRunner> cr;
        oe->getRunners(c_it->getId(), 0, cr, false);
        for (pRunner r : cr) {
          if (r->getStatus() == StatusNotCompetiting || r->getStatus() == StatusCANCEL)
            continue;
          if (r->getStartGroup(true) == ci.startGroupId)
            nr++;
        }
      }

      if (ci.nVacant == -1 || !ci.nVacantSpecified || di.changedVacancyInfo) {
        // Auto initialize
        int nVacancies = int(nr * di.vacancyFactor + 0.5);
        nVacancies = max(nVacancies, di.minVacancy);
        nVacancies = min(nVacancies, di.maxVacancy);
        nVacancies = max(nVacancies, 0);

        if (di.vacancyFactor == 0)
          nVacancies = 0;

        ci.nVacant = nVacancies;
        ci.nVacantSpecified = false;
      }

      if (!ci.nExtraSpecified || di.changedExtraInfo) {
        // Auto initialize
        ci.nExtra = max(int(nr * di.extraFactor + 0.5), 1);

        if (di.extraFactor == 0)
          ci.nExtra = 0;
        ci.nExtraSpecified = false;
      }

      ci.nRunners = nr + ci.nVacant;

      if (ci.nRunners > 0) {
        nRunnersTot += ci.nRunners + ci.nExtra;
        cInfo.push_back(ci);
        runnerPerGroup[ci.unique] += ci.nRunners + ci.nExtra;
        runnerPerCourse[ci.courseId] += ci.nRunners + ci.nExtra;
      }
    }

    maxGroup = 0;
    maxCourse = 0;
    maxNRunner = 0;
    int a = 1 + (alteration % 7);
    int b = (alteration % 3);
    int c = alteration % 5;

    for (size_t k = 0; k < cInfo.size(); k++) {
      maxNRunner = max(maxNRunner, cInfo[k].nRunners);
      cInfo[k].nRunnersGroup = runnerPerGroup[cInfo[k].unique];
      cInfo[k].nRunnersCourse = runnerPerCourse[cInfo[k].courseId];
      maxGroup = max(maxGroup, cInfo[k].nRunnersGroup);
      maxCourse = max(maxCourse, cInfo[k].nRunnersCourse);
      cInfo[k].sortFactor = cInfo[k].nRunners * a + cInfo[k].nRunnersGroup * b + cInfo[k].nRunnersCourse * c;
    }

    /*for (size_t k = 0; k < cInfo.size(); k++) {
      auto pc = oe->getClass(cInfo[k].classId);
      auto c = pc->getCourse();
      wstring cc;
      for (int i = 0; i < 3; i++)
        cc += itow(c->getControl(i)->getId()) + L",";
      wstring w = pc->getName() + L"; " + cc + L"; " + itow(cInfo[k].unique) + L"; " + itow(cInfo[k].nRunners) + L"\n";
      OutputDebugString(w.c_str());
    }*/

    di.numDistinctInit = runnerPerGroup.size();
    di.numRunnerSameInitMax = maxGroup;
    di.numRunnerSameCourseMax = maxCourse;
    // Calculate the theoretical best end position to use.
    bestEndPos = 0;

    for (map<int, int>::iterator it = runnerPerGroup.begin(); it != runnerPerGroup.end(); ++it) {
      vector< pair<int, int> > classes;
      for (size_t k = 0; k < cInfo.size(); k++) {
        if (cInfo[k].unique == it->first)
          classes.push_back(make_pair(cInfo[k].nRunners, cInfo[k].nExtra));
      }
      int optTime = optimalLayout(di.minClassInterval / di.baseInterval, classes);
      bestEndPos = max(optTime, bestEndPos);
    }

    if (nRunnersTot > 0)
      bestEndPos = max(bestEndPos, nRunnersTot / di.nFields);

    if (!di.allowNeighbourSameCourse)
      bestEndPos = max(bestEndPos, maxCourse * 2 - 1);
    else
      bestEndPos = max(bestEndPos, maxCourse);
  }

public:

  DrawOptimAlgo(oEvent *oe) : oe(oe) {
    oe->getClasses(Classes, false);
    oe->getRunners(-1, -1, Runners);
  }

  void optimizeStartOrder(vector< vector<pair<int, int> > > &StartField, DrawInfo &di,
                          vector<ClassInfo> &cInfo, int useNControls, int alteration) {

    if (bestEndPosGlobal == 0) {
      bestEndPosGlobal = 100000;
      for (int i = 1; i <= di.maxCommonControl && i<=10; i++) {
        int ncc = i;
        if (i >= 10)
          ncc = 0;
        computeBestStartDepth(di, cInfo, ncc, 0);
        bestEndPosGlobal = min(bestEndPos, bestEndPosGlobal);
      }

      di.minimalStartDepth = bestEndPosGlobal * di.baseInterval;
    }

    computeBestStartDepth(di, cInfo, useNControls, alteration);

    ClassInfo::sSortOrder = 0;
    sort(cInfo.begin(), cInfo.end());

    int maxSize = di.minClassInterval * maxNRunner;

    // Special case for constant time start
    if (di.baseInterval == 0) {
      di.baseInterval = 1;
      di.minClassInterval = 0;
    }
    int startGroup = 0;
    // Calculate an estimated maximal class intervall
    for (size_t k = 0; k < cInfo.size(); k++) {
      if (cInfo[k].startGroupId != 0)
        startGroup = cInfo[k].startGroupId; // Need to be constant
      int quotient = maxSize / (cInfo[k].nRunners*di.baseInterval);

      if (quotient*di.baseInterval > di.maxClassInterval)
        quotient = di.maxClassInterval / di.baseInterval;

      if (cInfo[k].nRunnersGroup >= maxGroup)
        quotient = di.minClassInterval / di.baseInterval;

      if (!cInfo[k].hasFixedTime)
        cInfo[k].interval = quotient;
    }

    for (int m = 0; m < di.nFields; m++)
      StartField[m].resize(3000);

    int alternator = 0;

    // Fill up with non-drawn classes
    for (auto &it : Runners) {
      if (it->isRemoved())
        continue;
      int st = it->getStartTime();
      int relSt = st - di.firstStart;
      int relPos = relSt / di.baseInterval;

      if (st > 0 && relSt >= 0 && relPos < 3000 && (relSt%di.baseInterval) == 0) {
        int cid = it->getClassId(true);
        if (otherClasses.count(cid) == 0) {
          if (startGroup == 0 || startGroup == it->getStartGroup(true))
            continue; 
        }
        pClass cls = it->getClassRef(true);
        if (cls) {
          if (!di.startName.empty() && cls->getStart() != di.startName)
            continue;

          if (cls->hasFreeStart())
            continue;
        }

        int unique, courseId;
        auto res = otherClasses.find(cid);
        if (res != otherClasses.end()) {
          unique = res->second.unique;
          courseId = res->second.courseId;
        }
        else {
          res = di.classes.find(cid);
          if (res != di.classes.end()) {
            unique = res->second.unique;
            courseId = res->second.courseId;
          }
          else {
            int unique = 12345678;
            int courseId = it->getCourse(false) ? it->getCourse(false)->getId() : 0;
          }
        }

        int k = 0;
        while (true) { 
          if (k == StartField.size()) {
            StartField.push_back(vector< pair<int, int> >());
            StartField.back().resize(3000);
          }
          if (StartField[k][relPos].first == 0) {
            StartField[k][relPos].first = unique;
            StartField[k][relPos].second = courseId;
            break;
          }
          k++;
        }
      }
    }

    // Fill up classes with fixed starttime
    for (size_t k = 0; k < cInfo.size(); k++) {
      if (cInfo[k].hasFixedTime) {
        insertStart(StartField, di.nFields, cInfo[k]);
      }
    }

    if (di.minClassInterval == 0) {
      // Set fixed start time
      for (size_t k = 0; k < cInfo.size(); k++) {
        if (cInfo[k].hasFixedTime)
          continue;
        cInfo[k].firstStart = di.firstStart;
        cInfo[k].interval = 0;
      }
    }
    else {
      // Do the distribution
      for (size_t k = 0; k < cInfo.size(); k++) {
        if (cInfo[k].hasFixedTime)
          continue;

        int minPos = 1000000;
        int minEndPos = 1000000;
        int minInterval = cInfo[k].interval;
        int startV = di.minClassInterval / di.baseInterval;
        int endV = cInfo[k].interval;

        if (cInfo[k].fixedInterval != 0) {
          endV = startV = cInfo[k].fixedInterval;
        }

        for (int i = startV; i <= endV; i++) {

          int startpos = alternator % max(1, (bestEndPos - cInfo[k].nRunners * i) / 3);
          startpos = 0;
          int ipos = startpos;
          int t = 0;

          while (!isFree(di, StartField, di.nFields, ipos, i, cInfo[k])) {
            t++;

            // Algorithm to randomize start position
            // First startpos -> bestEndTime, then 0 -> startpos, then remaining
            if (t < (bestEndPos - startpos))
              ipos = startpos + t;
            else {
              ipos = t - (bestEndPos - startpos);
              if (ipos >= startpos)
                ipos = t;
            }
          }

          int endPos = ipos + i * cInfo[k].nRunners;
          if (endPos < minEndPos || endPos < bestEndPos) {
            minEndPos = endPos;
            minPos = ipos;
            minInterval = i;
          }
        }

        cInfo[k].firstStart = minPos;
        cInfo[k].interval = minInterval;
        cInfo[k].overShoot = max(minEndPos - bestEndPosGlobal, 0);
        insertStart(StartField, di.nFields, cInfo[k]);

        alternator += alteration;
      }
    }
  }
};

void oEvent::optimizeStartOrder(vector<pair<int, wstring>> &outLines, DrawInfo &di, vector<ClassInfo> &cInfo)
{
  if (Classes.size()==0)
    return;

  struct StartParam {
    int nControls;
    int alternator;
    double badness;
    int last;
    StartParam() : nControls(1), alternator(1), badness(1000), last(90000000) {}
  };

  StartParam opt;
  bool found = false;
  int nCtrl = 1;//max(1, di.maxCommonControl-2);
  const int maxControlDiff = di.maxCommonControl < 1000 ? di.maxCommonControl : 10;
  bool checkOnlyClass = di.maxCommonControl == 1000;

  DrawOptimAlgo drawOptim(this);

  while (!found) {

    StartParam optInner;
    for (int alt = 0; alt <= 20 && !found; alt++) {
      vector< vector<pair<int, int> > > startField(di.nFields);
      drawOptim.optimizeStartOrder(startField, di, cInfo, nCtrl, alt);

      int overShoot = 0;
      int overSum = 0;
      int numOver = 0;
      for (size_t k=0;k<cInfo.size();k++) {
        const ClassInfo &ci = cInfo[k];
        if (ci.overShoot>0) {
          numOver++;
          overShoot = max (overShoot, ci.overShoot);
          overSum += ci.overShoot;
        }
        //laststart=max(laststart, ci.firstStart+ci.nRunners*ci.interval);
      }
      double avgShoot = double(overSum)/cInfo.size();
      double badness = overShoot==0 ? 0 : overShoot / avgShoot;

      if (badness<optInner.badness) {
        optInner.badness = badness;
        optInner.alternator = alt;
        optInner.nControls = nCtrl;

        //Find last starter
        optInner.last = 0;
        for (int k=0;k<di.nFields;k++) {
          for (size_t j=0;j<startField[k].size(); j++)
            if (startField[k][j].first)
              optInner.last = max(optInner.last, int(j));
        }
      }
    }

    if (optInner.last < opt.last)
      opt = optInner;

    if (opt.badness < 2.0 && !checkOnlyClass) {
      found = true;
    }

    if (!found) {
      nCtrl++;
    }

    if (nCtrl == 4 && checkOnlyClass)
      nCtrl = 1000;

    if (nCtrl>maxControlDiff) //We need some limit
      found = true;
  }

  vector< vector<pair<int, int> > > startField(di.nFields);
  drawOptim.optimizeStartOrder(startField, di, cInfo, opt.nControls, opt.alternator);

  outLines.emplace_back(0, L"Identifierar X unika inledningar på banorna.#" + itow(di.numDistinctInit));
  outLines.emplace_back(0, L"Största gruppen med samma inledning har X platser.#" + itow(di.numRunnerSameInitMax));
  outLines.emplace_back(0, L"Antal löpare på vanligaste banan X.#" + itow(di.numRunnerSameCourseMax));
  outLines.emplace_back(0, L"Kortast teoretiska startdjup utan krockar är X minuter.#" + itow(di.minimalStartDepth/timeConstMinute));
  outLines.emplace_back(0, L"");
  //Find last starter
  int last = opt.last;

  int laststart=0;
  for (size_t k=0;k<cInfo.size();k++) {
    const ClassInfo &ci = cInfo[k];
    laststart=max(laststart, ci.firstStart+(ci.nRunners-1)*ci.interval);
  }

  outLines.emplace_back(0, L"Faktiskt startdjup: X minuter.#" + itow(((last+1) * di.baseInterval)/timeConstMinute));

  outLines.emplace_back(1, L"Sista start (nu tilldelad): X.#" +
                        oe->getAbsTime(laststart*di.baseInterval+di.firstStart));

  outLines.emplace_back(0, L"");

  int nr;
  int T=0;
  int sum=0;
  outLines.emplace_back(1, L"Antal startande per intervall (inklusive redan lottade):");
  string str="";
  int empty=4;

  while (T <= last) {
    nr=0;
    for(size_t k=0;k<startField.size();k++){
      if (startField[k][T].first)
        nr++;
    }
    T++;
    sum+=nr;
    if (nr!=0) empty=4;
    else empty--;

    char bf[20];
    sprintf_s(bf, 20, "%d ", nr);
    str+=bf;
  }

  outLines.emplace_back(10, L"#" + gdibase.widen(str));
  outLines.emplace_back(0, L"");
}

void oEvent::loadDrawSettings(const set<int> &classes, DrawInfo &drawInfo, vector<ClassInfo> &cInfo) const {
  drawInfo.firstStart = timeConstHour * 22;
  drawInfo.minClassInterval = timeConstHour;
  drawInfo.maxClassInterval = 1;
  drawInfo.minVacancy = 10;
  drawInfo.maxVacancy = 1;
  drawInfo.changedExtraInfo = false;
  drawInfo.changedVacancyInfo = false;
  set<int> reducedStart;
  for (set<int>::const_iterator it = classes.begin(); it != classes.end(); ++it) {
    pClass pc = oe->getClass(*it);
    if (pc) {
      int fs = pc->getDrawFirstStart();
      int iv = pc->getDrawInterval();
      if (iv > 0 && fs > 0) {
        drawInfo.firstStart = min(drawInfo.firstStart, fs);
        drawInfo.minClassInterval = min(drawInfo.minClassInterval, iv);
        drawInfo.maxClassInterval = max(drawInfo.maxClassInterval, iv);
        drawInfo.minVacancy = min(drawInfo.minVacancy, pc->getDrawVacant());
        drawInfo.maxVacancy = max(drawInfo.maxVacancy, pc->getDrawVacant());
        reducedStart.insert(fs%iv);
      }
    }
  }

  drawInfo.baseInterval = drawInfo.minClassInterval;
  int lastStart = -1;
  for (set<int>::iterator it = reducedStart.begin(); it != reducedStart.end(); ++it) {
    if (lastStart == -1)
      lastStart = *it;
    else {
      drawInfo.baseInterval = min(drawInfo.baseInterval, *it-lastStart);
      lastStart = *it;
    }
  }

  map<int, int> runnerPerGroup;
  map<int, int> runnerPerCourse;

  cInfo.clear();
  cInfo.resize(classes.size());
  int i = 0;
  for (set<int>::const_iterator it = classes.begin(); it != classes.end(); ++it) {
    pClass pc = oe->getClass(*it);
    if (pc) {
      int fs = pc->getDrawFirstStart();
      int iv = pc->getDrawInterval();
      if (iv <= 0)
        iv = drawInfo.minClassInterval;
      if (fs <= 0)
        fs = drawInfo.firstStart; //Fallback
      
      cInfo[i].pc = pc;
      cInfo[i].classId = *it;
      cInfo[i].courseId = pc->getCourseId();
      cInfo[i].firstStart = fs;
      cInfo[i].unique = pc->getCourseId();
      if (cInfo[i].unique == 0)
        cInfo[i].unique = pc->getId() * 10000;
      cInfo[i].firstStart = (fs - drawInfo.firstStart) / drawInfo.baseInterval;
      cInfo[i].interval = iv / drawInfo.baseInterval;
      cInfo[i].nVacant = pc->getDrawVacant();
      cInfo[i].nExtra = pc->getDrawNumReserved();
      auto spec = pc->getDrawSpecification();
      cInfo[i].hasFixedTime = spec.count(oClass::DrawSpecified::FixedTime) != 0;
      cInfo[i].nExtraSpecified = spec.count(oClass::DrawSpecified::Extra) != 0;
      cInfo[i].nVacantSpecified = spec.count(oClass::DrawSpecified::Vacant) != 0;

      cInfo[i].nRunners = pc->getNumRunners(true, true, true) + cInfo[i].nVacant;

      if (cInfo[i].nRunners > 0) {
        runnerPerGroup[cInfo[i].unique] += cInfo[i].nRunners;
        runnerPerCourse[cInfo[i].courseId] += cInfo[i].nRunners;
      }
      drawInfo.classes[*it] = cInfo[i];
      i++;
    }
  }

  for (size_t k = 0; k<cInfo.size(); k++) {
    cInfo[k].nRunnersGroup = runnerPerGroup[cInfo[k].unique];
    cInfo[k].nRunnersCourse = runnerPerCourse[cInfo[k].courseId];
  }
}

void oEvent::drawRemaining(const set<int>& classSel, DrawMethod method, bool placeAfter)
{
  bool all = classSel.size() == 1 && *classSel.begin() == -1;
  DrawType drawType = placeAfter ? DrawType::RemainingAfter : DrawType::RemainingBefore;

  for (oClassList::iterator it = Classes.begin(); it !=  Classes.end(); ++it) {
    if (it->isRemoved())
      continue;
    if (all || classSel.count(it->getId())) {
      vector<ClassDrawSpecification> spec;
      spec.emplace_back(it->getId(), 0, 0, 0, 0, VacantPosition::Mixed);
      drawList(spec, method, 1, drawType);
    }
  }
}

struct GroupInfo {
  int firstStart = 0;
  int unassigned = 0;
  int ix;
  vector<int> rPerGroup;
  vector<int> vacantPerGroup;
};

namespace {
  bool sameFamily(pRunner a, pRunner b) {
    wstring af = a->getFamilyName();
    wstring bf = b->getFamilyName();
    if (af.length() == bf.length())
      return af == bf;
    if (af.length() > bf.length())
      swap(af, bf);

    vector<wstring> bff;
    split(bf, L" -", bff);
    for (wstring &bfs : bff) {
      if (bfs == af)
        return true;
    }

    return false;
    //return af.find(bf) != wstring::npos || bf.find(af) != wstring::npos;
  }

  template<typename RND>
  void groupUnassignedRunners(vector<pRunner> &rIn,
                              vector<pair<vector<pRunner>, bool>> &rGroups, 
                              int maxPerGroup, RND rnd) {

    map<int, vector<pRunner>> families;
    for (pRunner &r : rIn) {
      int fam = r->getDCI().getInt("Family");
      if (fam != 0) {
        families[fam].push_back(r);
        r = nullptr;
      }
    }

    map<int, vector<pRunner>> clubs;
    for (pRunner &r : rIn) {
      if (r)
        clubs[r->getClubId()].push_back(r);
    }

    // Merge families to clubs, if appropriate
    for (auto &fam : families) {
      int cid = fam.second[0]->getClubId();
      if (cid != 0 && clubs.count(cid) && int(clubs[cid].size() + fam.second.size()) < maxPerGroup) {
        clubs[cid].insert(clubs[cid].end(), fam.second.begin(), fam.second.end());
        fam.second.clear();
      }
    }

    vector<vector<pRunner>> rawGroups;
    for (auto &g : families) {
      if (g.second.size() > 0) {
        rawGroups.emplace_back();
        rawGroups.back().swap(g.second);
      }
    }

    for (auto &g : clubs) {
      if (g.second.size() > 0) {
        rawGroups.emplace_back();
        rawGroups.back().swap(g.second);
      }
    }

    shuffle(rawGroups.begin(), rawGroups.end(), rnd);

    stable_sort(rawGroups.begin(), rawGroups.end(), 
         [](const vector<pRunner> &a, const vector<pRunner> &b) {return (a.size()/4) < (b.size()/4); });

    for (auto &g : rawGroups) {
      if (int(g.size()) <= maxPerGroup)
        rGroups.emplace_back(g, false);
      else {
        int nSplit = (g.size() + maxPerGroup - 1) / maxPerGroup;
        sort(g.begin(), g.end(), [](const pRunner &a, const pRunner &b) {
          wstring n1 = a->getFamilyName();
          wstring n2 = b->getFamilyName();
          return n1 < n2;
        });

        vector<vector<pRunner>> famGroups(1);
        for (pRunner r : g) {
          if (famGroups.back().empty())
            famGroups.back().push_back(r);
          else {
            if (!sameFamily(famGroups.back().back(), r))
              famGroups.emplace_back();
            famGroups.back().push_back(r);
          }
        }

        shuffle(famGroups.begin(), famGroups.end(), rnd);

        stable_sort(famGroups.begin(), famGroups.end(),
                    [](const vector<pRunner> &a, const vector<pRunner> &b) {return (a.size() / 4) < (b.size() / 4); });

        size_t nPerGroup = g.size() / nSplit + 1;
        bool brk = false;
        while (!famGroups.empty()) {
          rGroups.emplace_back(vector<pRunner>(), brk);
          auto &dst = rGroups.back().first;
          brk = true;
          while (!famGroups.empty() && dst.size() + famGroups.back().size() <= nPerGroup) {
            dst.insert(dst.end(), famGroups.back().begin(), famGroups.back().end());
            famGroups.pop_back();
          }
        }
      }
    }
  }

  void printGroups(gdioutput &gdibase, const list<oRunner> &Runners) {
    map<int, vector<const oRunner *>> rbg;
    for (const oRunner &r : Runners) {
      rbg[r.getStartGroup(true)].push_back(&r);
    }
    gdibase.dropLine();
    gdibase.addString("", 0, "List groups");
    gdibase.dropLine();

    map<int, int> ccCount;
    for (auto rr : rbg) {
      auto &vr = rr.second;
      sort(vr.begin(), vr.end(), [](const oRunner *a, const oRunner *b) {
        if (a->getClassId(true) != b->getClassId(true))
          return a->getClassId(true) < b->getClassId(true);
        else
          return a->getClubId() < b->getClubId(); });

      gdibase.dropLine();
      gdibase.addString("", 1, "Group: " + itos(rr.first));
      int cls = -1;

      for (const oRunner *r : vr) {
        if (cls != r->getClassId(true)) {
          gdibase.dropLine();
          gdibase.addString("", 1, r->getClass(true));
          cls = r->getClassId(true);
        }
        ++ccCount[cls];
        gdibase.addString("", 0, itow(ccCount[cls]) + L": " + r->getCompleteIdentification());
      }
    }
  }
}

void oEvent::drawListStartGroups(const vector<ClassDrawSpecification> &spec,
                                 DrawMethod method, int pairSize, DrawType drawType,
                                 bool limitGroupSize,
                                 DrawInfo *diIn) {
  int nParallel = -1;
  if (diIn)
    nParallel = diIn->nFields;
    
  constexpr bool logOutput = false;
  auto &sgMap = getStartGroups(true);
  if (sgMap.empty())
    throw meosException("No start group defined");

  map<int, GroupInfo> gInfo;
  vector<pair<int, int>> orderedStartGroups;

  auto overlap = [](const StartGroupInfo &a, const StartGroupInfo &b) {
    if (a.firstStart >= b.firstStart && a.firstStart < b.lastStart)
      return true;
    if (a.lastStart > b.firstStart && a.lastStart <= b.lastStart)
      return true;
    if (b.firstStart >= a.firstStart && b.firstStart < a.lastStart)
      return true;
    if (b.lastStart > a.firstStart && b.lastStart <= a.lastStart)
      return true;
    return false;
  };

  auto formatSG = [this](int id, const StartGroupInfo &a) {
    wstring w = itow(id);
    if (!a.name.empty())
      w += L"/" + a.name;

    w += L" (" + getAbsTime(a.firstStart) + makeDash(L"-") + getAbsTime(a.lastStart) + L")";
    return w;
  };

  for (auto &sg : sgMap) {
    orderedStartGroups.emplace_back(sg.second.firstStart, sg.first);
    // Check overlaps
    for (auto &sgOther : sgMap) {
      if (sg.first == sgOther.first)
        continue;

      if (overlap(sg.second, sgOther.second)) {
        wstring s1 = formatSG(sg.first, sg.second), s2 = formatSG(sgOther.first, sgOther.second);
        throw meosException(L"Startgrupperna X och Y överlappar.#" + s1 + L"#" + s2);
      }

    }

  }
  // Order by start time
  sort(orderedStartGroups.begin(), orderedStartGroups.end());

  int fs = orderedStartGroups[0].first; // First start
  vector<pair<int, int>> rPerGroupTotal;
  map<int, int> gId2Ix;
  for (auto &sg : orderedStartGroups) {
    gId2Ix[sg.second] = rPerGroupTotal.size();
    rPerGroupTotal.emplace_back(0, sg.second);
  }

  for (size_t k = 0; k < spec.size(); k++) {
    auto &s = spec[k];
    auto &gi = gInfo[s.classID];
    gi.ix = k;
    gi.firstStart = fs;
    gi.rPerGroup.resize(sgMap.size());
  }

  vector<pRunner> unassigned;
  int total = 0;
  for (auto &r : Runners) {
    if (r.isRemoved() || r.isVacant())
      continue;
    r.tmpStartGroup = 0;
    int clsId = r.getClassId(true);
    auto res = gInfo.find(clsId);
    if (res != gInfo.end()) {
      total++;
      int id = r.getStartGroup(false);
      auto idRes = gId2Ix.find(id);
      if (idRes != gId2Ix.end()) {
        ++res->second.rPerGroup[idRes->second];
        ++rPerGroupTotal[idRes->second].first;
      }
      else {
        if (id > 0)
          throw meosException(L"Startgrupp med id X tilldelad Y finns inte.#" + itow(id) + L"#" + r.getCompleteIdentification());
        ++res->second.unassigned;
        unassigned.push_back(&r);
      }
    }
  }

  vector<pair<vector<pRunner>, bool>> uaGroups;
  unsigned seed = (unsigned)std::chrono::system_clock::now().time_since_epoch().count();
  auto rnd = std::default_random_engine(seed);

  int maxPerGroup = Runners.size();
  if (limitGroupSize)
    maxPerGroup = max(int(Runners.size() / sgMap.size()) / 4, 4);

  groupUnassignedRunners(unassigned, uaGroups, maxPerGroup, rnd );

  int nPerGroupAvg = (total * 9) / (sgMap.size() * 10);
  shuffle(rPerGroupTotal.begin(), rPerGroupTotal.end(), rnd);

  // Assign to groups
  while (!uaGroups.empty()) {
    stable_sort(rPerGroupTotal.begin(), rPerGroupTotal.end(), 
                [](const pair<int,int> &a, const pair<int, int> &b) {return (a.first / 4) < (b.first / 4); });

    // Setup map to next start group
    map<int, int> nextGroup;
    auto getNextGroup = [&](int ix) {
      if (nextGroup.empty()) {
        for (size_t k = 0; k < rPerGroupTotal.size(); k++) {
          int srcId = rPerGroupTotal[k].second;
          for (size_t j = 0; j < orderedStartGroups.size(); j++) {
            if (orderedStartGroups[j].second == srcId) {
              int nextGrpId;
              if (j + 1 < orderedStartGroups.size())
                nextGrpId = orderedStartGroups[j + 1].second;
              else
                nextGrpId = orderedStartGroups[0].second;

              for (size_t kk = 0; kk < rPerGroupTotal.size(); kk++) {
                if (rPerGroupTotal[kk].second == nextGrpId) {
                  nextGroup[k] = kk;
                  break;
                }
              }
              break;
            }
          }
        }
      }
      return nextGroup[ix];
    };

    int nextGroupToUse = -1;
    for (size_t k = 0; k < rPerGroupTotal.size(); k++) {
      if (nextGroupToUse == -1)
        nextGroupToUse = k;

      int &nGroup = rPerGroupTotal[nextGroupToUse].first;
      int groupId = rPerGroupTotal[nextGroupToUse].second;
      int currentGroup = nextGroupToUse;
      nextGroupToUse = -1;

      if (logOutput) {
        gdibase.dropLine();
        gdibase.addString("", 1, "Group: " + itos(groupId) + " (" + itos(nGroup) + ")");
      }

      while (nGroup <= nPerGroupAvg && !uaGroups.empty()) {
        for (pRunner ua : uaGroups.back().first) {
          ua->tmpStartGroup = groupId;
          auto &gi = gInfo[ua->getClassId(true)];
          int j = gId2Ix[groupId];
          ++gi.rPerGroup[j];
          nGroup++;
        }
        bool skip = uaGroups.back().second;
        uaGroups.pop_back();
        if (skip) {
          nextGroupToUse = getNextGroup(currentGroup);
          break; // Assign to next group (same club)
        }
      }
    }
    nPerGroupAvg++; // Ensure convergance
  }

  //  vacantPerGroup
  for (size_t k = 0; k < spec.size(); k++) {
    auto &gi = gInfo[spec[k].classID];
    gi.vacantPerGroup.resize(sgMap.size());
    for (int j = 0; j < spec[k].vacances; j++)
      ++gi.vacantPerGroup[GetRandomNumber(sgMap.size())];
  }

  if (logOutput)
    printGroups(gdibase, Runners);

  map<int, int> rPerGroup;
  // Ensure not too many competitors in same class per groups
  vector<map<int, map<int, int>>> countClassGroupClub(spec.size());
  vector<pRunner> rl;
  map<int, double> classFractions;
  map<pair<int,int>, double> clubFractions;
  int rTot = 0;
  for (size_t k = 0; k < spec.size(); k++) {
    auto &countGroupClub = countClassGroupClub[k];
    int cls = spec[k].classID;
    getRunners(cls, 0, rl, false);
    classFractions[cls] = (double)rl.size();
    rTot += rl.size();
    for (pRunner r : rl) {
      int gid = r->getStartGroup(true);
      ++rPerGroup[gid];
      int club = r->getClubId();
      if (club != 0) {
        ++clubFractions[make_pair(club,cls)];
        ++countGroupClub[gid][club]; // Count per club
      }
      ++countGroupClub[gid][0]; // Count all
    }
  }
  double q = 1.0 / rTot;
  for (auto &e : classFractions)
    e.second *= q;

  for (auto &e : clubFractions)
    e.second *= (q / classFractions[e.first.second]);

  vector<tuple<int, int, int, int>> moveFromGroupClassClub;
  // For each start group, count class members in each class and per club
  vector<pair<int, int>> numberClub;
  for (size_t j = 0; j < orderedStartGroups.size(); j++) {
    auto &g = orderedStartGroups[j];
    int groupId = g.second;
    
    for (size_t k = 0; k < spec.size(); k++) {
      auto &countGroupClub = countClassGroupClub[k];
      int cls = spec[k].classID;
      const int nTotal = countGroupClub[groupId][0];
      int clubSwitch = 0; // 0 any club, or specified club id.
      int numNeedSwitch = 0;
      if (nTotal > 3) {
        numberClub.clear();
        for (auto &clubCount : countGroupClub[groupId]) {
          if (clubCount.first != 0)
            numberClub.emplace_back(clubCount.second, clubCount.first);
        }
        if (numberClub.empty())
          continue;
        
        sort(numberClub.begin(), numberClub.end());
        int clbId = numberClub.back().second;
        int limit = max<int>(nTotal / 2, int(nTotal*clubFractions[make_pair(clbId, cls)]));
        int limitTot = int(rPerGroup[groupId] * max(0.3, classFractions[cls]));
        if (numberClub.size() == 1 || numberClub.back().first > limit) {
          numNeedSwitch = numberClub.back().first - limit;
          clubSwitch = clbId;
        }
        else if (nTotal > limitTot) {
          // Move from any club
          numNeedSwitch = nTotal - limitTot;
          clubSwitch = 0;
        }
      }

      if (numNeedSwitch > 0) {
        moveFromGroupClassClub.emplace_back(groupId, cls, clubSwitch, numNeedSwitch);
      }
    }
  }

  if (moveFromGroupClassClub.size() > 0 && limitGroupSize) {
    vector<vector<pRunner>> runnersPerGroup(orderedStartGroups.size());
    map<int, int> groupId2Ix;
    for (size_t j = 0; j < orderedStartGroups.size(); j++)
      groupId2Ix[orderedStartGroups[j].second] = j;

    for (size_t k = 0; k < spec.size(); k++) {
      getRunners(spec[k].classID, 0, rl, false);
      for (pRunner r : rl) {
        runnersPerGroup[groupId2Ix[r->tmpStartGroup]].push_back(r);
      }
    }

    for (size_t j = 0; j < orderedStartGroups.size(); j++) {
      shuffle(runnersPerGroup[j].begin(), runnersPerGroup[j].end(), rnd);
      stable_sort(runnersPerGroup[j].begin(), runnersPerGroup[j].end(), [](pRunner a, pRunner b) {
        int specA = a->getStartGroup(false);
        int specB = b->getStartGroup(false);
        if (specA != specB)
          return specA < specB;
        return a->getEntryDate(false) > b->getEntryDate(false);
      });
    }

    map<int, map<pair<int, int>, int>> classClubCountByGroup;
    for (auto ms : moveFromGroupClassClub) {
      int groupId = std::get<0>(ms);
      int clsId = std::get<1>(ms);
      int clubId = std::get<2>(ms);
      int cnt = std::get<3>(ms);
      classClubCountByGroup[groupId][make_pair(clsId, clubId)] = cnt;
    }

    for (size_t j = 0; j < orderedStartGroups.size(); j++) {
      int thisGroupId = orderedStartGroups[j].second;
      auto &classClubCount = classClubCountByGroup[thisGroupId];
      for (pRunner &r : runnersPerGroup[j]) {
        if (!r)
          continue;
        int cls = r->getClassId(true);
        int club = r->getClubId();
        auto res = classClubCount.find(make_pair(cls, club));
        int type = 0;
        if (res != classClubCount.end()) {
          if (res->second > 0) {
            --res->second;
            type = 1;
          }
        }
        else {
          res = classClubCount.find(make_pair(cls, 0));
          if (res != classClubCount.end()) {
            if (res->second > 0) {
              --res->second;
              type = 2;
            }
          }
        }
        if (type == 0)
          continue; //do not move

         constexpr int done = 100;
        for (int iter = 0; iter < 5; iter++) {
          int nextG = -1;
          if ((iter & 1) == 0)
            nextG = j  + (iter / 2 + 1);
          else
            nextG = j - (iter / 2 + 1);

          if (size_t(nextG) >= runnersPerGroup.size())
            continue;

          int nextGroupId = orderedStartGroups[nextG].second;
          auto &nextClassClubCount = classClubCountByGroup[nextGroupId];

          if (type == 1 && nextClassClubCount.find(make_pair(cls, club)) != nextClassClubCount.end())
            continue;

          for (pRunner &rr : runnersPerGroup[nextG]) {
            if (rr) {
              if (type == 1 && rr->getClassId(true) == cls && rr->getClubId() != club) {
                rr->tmpStartGroup = orderedStartGroups[j].second;
                r->tmpStartGroup = orderedStartGroups[nextG].second;
                rr = nullptr;
                r = nullptr;
                iter = done;
                break;
              }
              else if (type == 2 && rr->getClassId(true) != cls) {
                rr->tmpStartGroup = orderedStartGroups[j].second;
                r->tmpStartGroup = orderedStartGroups[nextG].second;
                rr = nullptr;
                r = nullptr;
                iter = done;
                break;
              }
            }
          }
        }
        
      }
    }
  }

  if (logOutput) {
    gdibase.addString("", 0, "Reordered groups");
    printGroups(gdibase, Runners);
  }
  
  if (spec.size() == 1) {
    for (size_t j = 0; j < orderedStartGroups.size(); j++) {
      auto &sg = orderedStartGroups[j];
      int groupId = sg.second;
      int firstStart = getStartGroup(groupId).firstStart;

      vector<ClassDrawSpecification> specLoc = spec;
      for (size_t k = 0; k < specLoc.size(); k++) {
        auto &gi = gInfo[specLoc[k].classID];
        specLoc[k].startGroup = groupId;
        specLoc[k].firstStart = max(gi.firstStart, firstStart);
        specLoc[k].vacances = gi.vacantPerGroup[j];
        gi.firstStart = specLoc[k].firstStart + specLoc[k].interval * (specLoc[k].vacances + gi.rPerGroup[j]);
      }
      drawList(specLoc, method, pairSize, drawType);
    }
  }
  else {
    int leg = spec[0].leg;
    VacantPosition vp = VacantPosition::Mixed;
    DrawInfo di;
    di.baseInterval = timeConstMinute;
    di.allowNeighbourSameCourse = true;

    di.extraFactor = 0;
    di.minClassInterval = di.baseInterval * 2;
    di.maxClassInterval = di.baseInterval * 8;

    di.minVacancy = 1;
    di.maxVacancy = 100;
    di.vacancyFactor = 0;
    if (diIn) {
      di.baseInterval = diIn->baseInterval;
      di.minClassInterval = diIn->minClassInterval;
      di.maxCommonControl = diIn->maxCommonControl;
      di.allowNeighbourSameCourse = diIn->allowNeighbourSameCourse;
    }

    // Update runner per group/class counters
    for (auto &gi : gInfo) {
      fill(gi.second.rPerGroup.begin(), gi.second.rPerGroup.end(), 0);
    }

    rPerGroup.clear();
    for (size_t k = 0; k < spec.size(); k++) {
      getRunners(spec[k].classID, 0, rl, false);
      auto &gi = gInfo[spec[k].classID];
      for (pRunner r : rl) {
        r->setStartTime(0, true, oBase::ChangeType::Update, false);
        int gid = r->getStartGroup(true);
        ++rPerGroup[gid];
        ++gi.rPerGroup[gId2Ix[gid]];
      }
    }

    map<int, int> rPerGroup;
    for (auto &gt : rPerGroupTotal)
      rPerGroup[gt.second] = gt.first;

    for (size_t j = 0; j < orderedStartGroups.size(); j++) {
      auto &sg = orderedStartGroups[j];
      int groupId = sg.second;
      
      int firstStart = getStartGroup(groupId).firstStart;

      int nspos = (oe->getStartGroup(groupId).lastStart - firstStart) / di.baseInterval;
      int optimalParallel = rPerGroup[groupId] / nspos;

      if (nParallel <= 0)
        di.nFields = max(3, min(optimalParallel + 2, 100));
      else
        di.nFields = nParallel;

      di.firstStart = firstStart;
      di.changedVacancyInfo = false;

      vector<ClassInfo> cInfo;
      vector<pair<int, wstring>> outLines;
      di.vacancyFactor = 0;
      auto group = sgMap.find(groupId);
      int length = max(300, group->second.lastStart - group->second.firstStart);
      int slots = length / di.baseInterval;
      di.classes.clear();
      for (size_t k = 0; k < spec.size(); k++) {
        auto &gi = gInfo[spec[k].classID];
        int rClassGroup = gi.rPerGroup[j];
        if (rClassGroup == 0)
          continue;

        di.classes[spec[k].classID] = ClassInfo(getClass(spec[k].classID));
        ClassInfo &ci = di.classes[spec[k].classID];
        ci.startGroupId = groupId;
        if (gi.firstStart > firstStart) {
         // Handled by distributor
         // ci.firstStart = (gi.firstStart - firstStart) / di.baseInterval;
         // ci.hasFixedTime = true;
        }
        ci.nVacant = gi.vacantPerGroup[j];
        ci.nVacantSpecified = true;
        ci.nExtraSpecified = true;
        
        if (rClassGroup == 1)
          ci.fixedInterval = 4;
        else {
          int q = slots / (rClassGroup-1);
          if (q >= 12)
            ci.fixedInterval = 8;
          else if (q >= 6)
            ci.fixedInterval = 4;
          else 
            ci.fixedInterval = 2;
        }
        if (ci.fixedInterval > 2)
          ci.interval = ci.fixedInterval;
        else
          ci.interval = spec[k].interval / di.baseInterval;
      }
      
      if (logOutput) {
        gdibase.dropLine();
        gdibase.addString("", 1, "Behandlar grupp: " + itos(groupId));
      }

      optimizeStartOrder(outLines, di, cInfo);
      
      if (logOutput) {
        for (auto &ol : outLines) {
          gdibase.addString("", ol.first, ol.second);
        }
      }

      int laststart = 0;
      for (size_t k = 0; k<cInfo.size(); k++) {
        const ClassInfo &ci = cInfo[k];
        laststart = max(laststart, ci.firstStart + ci.nRunners*ci.interval);
      }

      //gdi.addStringUT(1, lang.tl("Sista start (nu tilldelad)") + L": " +
      //                getAbsTime((laststart)*di.baseInterval + di.firstStart));


      for (size_t k = 0; k < cInfo.size(); k++) {
        ClassInfo &ci = cInfo[k];
        vector<ClassDrawSpecification> specx;
        specx.emplace_back(ci.classId, leg,
                          di.firstStart + di.baseInterval * ci.firstStart,
                          di.baseInterval * ci.interval, ci.nVacant, vp);

        auto &gi = gInfo[cInfo[k].classId];
        gi.firstStart = specx[0].firstStart + specx[0].interval * (specx[0].vacances + gi.rPerGroup[j]);

        specx.back().startGroup = groupId;
        drawList(specx, method, pairSize, drawType);
      }
    }
  }
}

void oEvent::drawList(const vector<ClassDrawSpecification> &spec, 
                      DrawMethod method, int pairSize, DrawType drawType) {

  autoSynchronizeLists(false);
  assert(pairSize > 0);
  oRunnerList::iterator it;

  int VacantClubId=getVacantClub(false);
  map<int, int> clsId2Ix;
  set<int> clsIdClearVac;

  const bool multiDay = hasPrevStage();

  for (size_t k = 0; k < spec.size(); k++) {
    pClass pc = getClass(spec[k].classID);

    if (!pc)
      throw std::exception("Klass saknas");

    if (spec[k].vacances>0 && pc->getClassType()==oClassRelay)
      throw std::exception("Vakanser stöds ej i stafett.");

    if (spec[k].vacances>0 && (spec[k].leg>0 || pc->getParentClass()))
      throw std::exception("Det går endast att sätta in vakanser på sträcka 1.");

    if (size_t(spec[k].leg) < pc->legInfo.size()) {
      pc->setStartType(spec[k].leg, STDrawn, true); //Automatically change start method
    }
    else if (spec[k].leg == -1) {
      for (size_t j = 0; j < pc->legInfo.size(); j++)
        pc->setStartType(j, STDrawn, true); //Automatically change start method
    }
    pc->synchronize(true);
    clsId2Ix[spec[k].classID] = k;
    if (!multiDay && spec[k].leg == 0 && pc->getParentClass() == 0)
      clsIdClearVac.insert(spec[k].classID);
  }

  vector<pRunner> runners;
  runners.reserve(Runners.size());

  if (drawType == DrawType::DrawAll) {
    
    if (!clsIdClearVac.empty()) {
      //Only remove vacances on leg 0.
      vector<int> toRemove;
      //Remove old vacances
      for (it = Runners.begin(); it != Runners.end(); ++it) {
        if (clsIdClearVac.count(it->getClassId(true))) {
          if (it->isRemoved())
            continue;
          if (it->tInTeam)
            continue; // Cannot remove team runners
          int k = clsId2Ix.find(it->getClassId(true))->second;
          if (spec[k].startGroup > 0 &&
              it->getStartGroup(true) > 0 &&
              it->getStartGroup(true) != spec[k].startGroup)
            continue;
          if (it->getClubId() == VacantClubId) {
            toRemove.push_back(it->getId());
          }
        }
      }

      removeRunner(toRemove);
      toRemove.clear();
      //loop over specs, check clsIdClearVac...
      
      for (size_t k = 0; k < spec.size(); k++) {
        if (!clsIdClearVac.count(spec[k].classID))
          continue;
        for (int i = 0; i < spec[k].vacances; i++) {
          oe->addRunnerVacant(spec[k].classID)->setStartGroup(spec[k].startGroup);
        }
      }
    }

    for (it=Runners.begin(); it != Runners.end(); ++it) {
      int cid = it->getClassId(true);
      if (!it->isRemoved() && clsId2Ix.count(cid)) {
        if (it->getStatus() == StatusNotCompetiting || it->getStatus() == StatusCANCEL)
          continue;
        int ix = clsId2Ix[cid];
        if (spec[ix].startGroup != 0 && it->getStartGroup(true) != spec[ix].startGroup)
          continue;
        
        if (it->legToRun() == spec[ix].leg || spec[ix].leg == -1 || it->getClassId(false) != cid ) {
          runners.push_back(&*it);
          spec[ix].ntimes++;
        }
      }
    }
  }
  else {
    // Find first/last start in class and interval:
    vector<int> first(spec.size(), 7*24*timeConstHour);
    vector<int> last(spec.size(), 0);
    set<int> cinterval;
    int baseInterval = 10*timeConstMinute;

    for (it=Runners.begin(); it != Runners.end(); ++it) {
      if (!it->isRemoved() && clsId2Ix.count(it->getClassId(true))) {
        if (it->getStatus() == StatusNotCompetiting || it->getStatus() == StatusCANCEL)
          continue;

        int st = it->getStartTime();
        int ix = clsId2Ix[it->getClassId(false)];
          
        if (st>0) {
          first[ix] = min(first[ix], st);
          last[ix] = max(last[ix], st);
          cinterval.insert(st);
        }
        else {
          spec[ix].ntimes++;
          runners.push_back(&*it);
        }
      }
    }

    // Find start interval
    int t=0;
    for (set<int>::iterator sit = cinterval.begin(); sit!=cinterval.end();++sit) {
      if ( (*sit-t) > 0)
        baseInterval = min(baseInterval, (*sit-t));
      t = *sit;
    }

    for (size_t k = 0; k < spec.size(); k++) {
      if (drawType == DrawType::RemainingBefore)
        spec[k].firstStart = first[k] - runners.size()*baseInterval;
      else
        spec[k].firstStart = last[k] + baseInterval;

      spec[k].interval = baseInterval;

      if (last[k] == 0 || spec[k].firstStart<=0 ||  baseInterval == 10*timeConstMinute) {
        // Fallback if incorrect specification.
        spec[k].firstStart = timeConstHour;
        spec[k].interval = 2*timeConstMinute;
      }
    }
  }

  if (runners.empty())
    return;

  vector<int> stimes(runners.size());
  int nr = 0;
  for (size_t k = 0; k < spec.size(); k++) {
    for (int i = 0; i < spec[k].ntimes; i++) {
      int kx = i/pairSize;
      stimes[nr++] = spec[k].firstStart + spec[k].interval * kx;
    }
  }
  
  if (spec.size() > 1)
    sort(stimes.begin(), stimes.end());

  if (gdibase.isTest())
    InitRanom(0,0);


  vector<pRunner> vacant;
  VacantPosition vp = spec[0].vacantPosition;
  if (vp != VacantPosition::Mixed) {
    // Move vacants to a dedicated container
    for (size_t k = 0; k < runners.size(); k++) {
      if (runners[k]->isVacant()) {
        vacant.push_back(runners[k]);
        std::swap(runners[k], runners.back());
        runners.pop_back();
        k--;
      }
    }
  }

  switch (method) {
  case DrawMethod::SOFT:
    drawSOFTMethod(runners, true);
    break;
  case DrawMethod::MeOS:
    drawMeOSMethod(runners);
    break;
  case DrawMethod::Random:
  {
    vector<int> pv(runners.size());
    for (size_t k = 0; k < pv.size(); k++)
      pv[k] = k;
    permute(pv);
    vector<pRunner> r2(runners.size());
    for (size_t k = 0; k < pv.size(); k++)
      r2[k] = runners[pv[k]];
    r2.swap(runners);
  }
    break;
  default:
    throw 0;
  }

  if (vp == VacantPosition::First) {
    runners.insert(runners.begin(), vacant.begin(), vacant.end());
  }
  else if (vp == VacantPosition::Last) {
    runners.insert(runners.end(), vacant.begin(), vacant.end());
  }

  int minStartNo = Runners.size();
  vector<pair<int, int>> newStartNo;
  for(unsigned k=0;k<stimes.size(); k++) {
    runners[k]->setStartTime(stimes[k], true, ChangeType::Update, false);
    runners[k]->synchronize();
    minStartNo = min(minStartNo, runners[k]->getStartNo());
    newStartNo.emplace_back(stimes[k], k);
  }
  /*
  gdibase.dropLine();
  gdibase.addString("", 1, L"Draw: " + oe->getClass(spec[0].classID)->getName());
  for (unsigned k = 0; k < stimes.size(); k++) {
    gdibase.addString("", 0, runners[k]->getCompleteIdentification() + L"  " + runners[k]->getStartTimeS());
  }
  */
  sort(newStartNo.begin(), newStartNo.end());
  //CurrentSortOrder = SortByStartTime;
  //sort(runners.begin(), runners.end());

  if (minStartNo == 0)
    minStartNo = nextFreeStartNo + 1;

  for(size_t k=0; k<runners.size(); k++) {
    pClass pCls = runners[k]->getClassRef(true);
    if (pCls && pCls->lockedForking() || runners[k]->getLegNumber() > 0)
      continue;
    runners[k]->updateStartNo(newStartNo[k].second + minStartNo);
  }

  nextFreeStartNo = max<int>(nextFreeStartNo, minStartNo + stimes.size());
}

void oEvent::drawListClumped(int ClassID, int FirstStart, int Interval, int Vacances)
{
  pClass pc=getClass(ClassID);

  if (!pc)
    throw std::exception("Klass saknas");

  if (Vacances>0 && pc->getClassType()!=oClassIndividual)
    throw std::exception("Lottningsmetoden stöds ej i den här klassen.");

  oRunnerList::iterator it;
  int nRunners=0;

  autoSynchronizeLists(false);

  while (Vacances>0) {
    addRunnerVacant(ClassID);
    Vacances--;
  }

  for (it = Runners.begin(); it != Runners.end(); ++it) {
    if (it->Class && it->Class->Id == ClassID && !it->isRemoved()) nRunners++;
  }
  if (nRunners==0) return;

  int *stimes=new int[nRunners];


  //Number of start groups
  //int ngroups=(nRunners/5)
  int ginterval;

  if (nRunners>=Interval)
    ginterval=10 * timeConstSecond;
  else if (Interval/nRunners>60*timeConstSecond){
    ginterval=40 * timeConstSecond;
  }
  else if (Interval/nRunners>30 * timeConstSecond){
    ginterval=20 * timeConstSecond;
  }
  else if (Interval/nRunners>20 * timeConstSecond){
    ginterval=15 * timeConstSecond;
  }
  else if (Interval/nRunners>10 * timeConstSecond){
    ginterval=12 * timeConstSecond;
  }
  else ginterval=10 * timeConstSecond;

  int nGroups=Interval/ginterval+1; //15 s. per interval.
  nGroups = min(nGroups, 2*nRunners+1);
  int k;

  if (nGroups>0){

    int MaxRunnersGroup=max((2*nRunners)/nGroups, 4)+GetRandomNumber(2);
    vector<int> sgroups(nGroups);

    for(k=0;k<nGroups; k++)
      sgroups[k]=FirstStart+((ginterval*k+2)/5)*5;

    if (nGroups>5){
      //Remove second group...
      sgroups[1]=sgroups[nGroups-1];
      nGroups--;
    }

    if (nGroups>9 && ginterval<60 && (GetRandomBit() || GetRandomBit() || GetRandomBit())){
      //Remove third group...
      sgroups[2]=sgroups[nGroups-1];
      nGroups--;

      if (nGroups>13 &&  ginterval<30 && (GetRandomBit() || GetRandomBit() || GetRandomBit())){
        //Remove third group...
        sgroups[3]=sgroups[nGroups-1];
        nGroups--;


        int ng=4; //Max two minutes pause
        while(nGroups>10 && (nRunners/nGroups)<MaxRunnersGroup && ng<8 && (GetRandomBit() || GetRandomBit())){
          //Remove several groups...
          sgroups[ng]=sgroups[nGroups-1];
          nGroups--;
          ng++;
        }
      }
    }

    //Permute some of the groups (not first and last group = group 0 and 1)
    if (nGroups>5){
      permute(sgroups.data() + 2, nGroups - 2); 

      //Remove some random groups (except first and last = group 0 and 1).
      for(k=2;k<nGroups; k++){
        if ((nRunners/nGroups)<MaxRunnersGroup && nGroups>5){
          sgroups[k]=sgroups[nGroups-1];
          nGroups--;
        }
      }
    }

    //Premute all groups;
    permute(sgroups.data(), nGroups);

    vector<int> counters(nGroups);
    
    stimes[0]=FirstStart;
    stimes[1]=FirstStart+Interval;

    for(k=2;k<nRunners; k++){
      int g=GetRandomNumber(nGroups);

      if (counters[g]<=2 && GetRandomBit()){
        //Prefer already large groups
        g=(g+1)%nGroups;
      }

      if (counters[g]>MaxRunnersGroup){
        g=(g+3)%nGroups;
      }

      if (sgroups[g]==FirstStart){
        //Avoid first start
        if (GetRandomBit() || GetRandomBit())
          g=(g+1)%nGroups;
      }

      if (counters[g]>MaxRunnersGroup){
        g=(g+2)%nGroups;
      }

      if (counters[g]>MaxRunnersGroup){
        g=(g+2)%nGroups;
      }

      stimes[k]=sgroups[g];
      counters[g]++;
    }
  }
  else{
    for(k=0;k<nRunners; k++) stimes[k]=FirstStart;
  }


  permute(stimes, nRunners);

  k=0;

  for (it = Runners.begin(); it != Runners.end(); ++it) {
    if (it->Class && it->Class->Id == ClassID && !it->isRemoved()) {
      it->setStartTime(stimes[k++], true, oBase::ChangeType::Update, false);
      it->StartNo = k;
      it->synchronize();
    }
  }
  reCalculateLeaderTimes(ClassID);

  delete[] stimes;
}

void oEvent::automaticDrawAll(gdioutput &gdi, 
                              const wstring &firstStart,
                              const wstring &minIntervall, 
                              const wstring &vacances, 
                              VacantPosition vp,
                              bool lateBefore, 
                              bool allowNeighbourSameCourse,
                              DrawMethod method, 
                              int pairSize) {
  gdi.refresh();
  const int leg = 0;
  const double extraFactor = 0.0;
  int drawn = 0;

  int baseInterval = convertAbsoluteTimeMS(minIntervall)/2;

  if (baseInterval == 0) {
    gdi.fillDown();
    int iFirstStart = getRelativeTime(firstStart);

    if (iFirstStart>0)
      gdi.addString("", 1, "Gemensam start");
    else {
      gdi.addString("", 1, "Nollställer starttider");
      iFirstStart = 0;
    }
    gdi.refreshFast();
    gdi.dropLine();
    for (oClassList::iterator it = Classes.begin(); it!=Classes.end(); ++it) {
      if (it->isRemoved())
        continue;
      vector<ClassDrawSpecification> spec;
      spec.emplace_back(it->getId(), 0, iFirstStart, 0, 0, vp);
      drawList(spec, DrawMethod::Random, 1, DrawType::DrawAll);
    }
    return;
  }

  if (baseInterval<timeConstSecond || baseInterval>timeConstHour)
    throw std::exception("Felaktigt tidsformat för intervall");

  int iFirstStart = getRelativeTime(firstStart);

  if (iFirstStart<=0)
    throw std::exception("Felaktigt tidsformat för första start");

  double vacancy = _wtof(vacances.c_str())/100;

  gdi.fillDown();
  gdi.addString("", 1, "Automatisk lottning").setColor(colorGreen);
  gdi.addString("", 0, "Inspekterar klasser...");
  gdi.refreshFast();

  set<int> notDrawn;
  getNotDrawnClasses(notDrawn, false);

  set<int> needsCompletion;
  getNotDrawnClasses(needsCompletion, true);

  for(set<int>::iterator it = notDrawn.begin(); it!=notDrawn.end(); ++it)
    needsCompletion.erase(*it);

  //Start with not drawn classes
  map<wstring, int> starts;
  map<pClass, int> runnersPerClass;

  // Count number of runners per start
  for (oRunnerList::iterator it = Runners.begin(); it!=Runners.end(); ++it) {
    if (it->skip())
      continue;
    if (it->tLeg != leg)
      continue;
    if (it->isVacant() && notDrawn.count(it->getClassId(false))==1)
      continue;
    pClass pc = it->Class;

    if (pc && pc->hasFreeStart())
      continue;

    if (pc)
      ++starts[pc->getStart()];

    ++runnersPerClass[pc];
  }

  while ( !starts.empty() ) {
    // Select smallest start
    int runnersStart = Runners.size()+1;
    wstring start;
    for ( map<wstring, int>::iterator it = starts.begin(); it != starts.end(); ++it) {
      if (runnersStart > it->second) {
        start = it->first;
        runnersStart = it->second;
      }
    }
    starts.erase(start);

    // Estimate parameters for start
    DrawInfo di;
    int maxRunners = 0;

    // Find largest class in start;
    for (oClassList::iterator it = Classes.begin(); it!=Classes.end(); ++it) {
      if (it->getStart() != start)
        continue;
      if (it->hasFreeStart() || it->hasRequestStart())
        continue;

      maxRunners = max(maxRunners, runnersPerClass[&*it]);
    }

    if (maxRunners==0)
      continue;

    if (getStartGroups(true).size() == 0) {

      int maxParallell = 15;

      if (runnersStart < 100)
        maxParallell = 4;
      else if (runnersStart < 300)
        maxParallell = 6;
      else if (runnersStart < 700)
        maxParallell = 10;
      else if (runnersStart < 1000)
        maxParallell = 12;
      else
        maxParallell = 15;

      int optimalParallel = runnersStart / (maxRunners * 2); // Min is every second interval

      di.nFields = max(3, min(optimalParallel + 2, 15));
      di.baseInterval = baseInterval;
      di.extraFactor = extraFactor;
      di.firstStart = iFirstStart;
      di.minClassInterval = baseInterval * 2;
      di.maxClassInterval = di.minClassInterval;

      di.minVacancy = 1;
      di.maxVacancy = 100;
      di.vacancyFactor = vacancy;
      di.allowNeighbourSameCourse = allowNeighbourSameCourse;

      di.startName = start;

      for (oClassList::iterator it = Classes.begin(); it != Classes.end(); ++it) {
        if (it->getStart() != start)
          continue;
        if (notDrawn.count(it->getId()) == 0)
          continue; // Only not drawn classes
        if (it->hasFreeStart() || it->hasRequestStart())
          continue;

        di.classes[it->getId()] = ClassInfo(&*it);
      }

      if (di.classes.size() == 0)
        continue;

      gdi.dropLine();
      gdi.addStringUT(1, lang.tl(L"Optimerar startfördelning ") + start);
      gdi.refreshFast();
      gdi.dropLine();
      vector<ClassInfo> cInfo;
      vector<pair<int, wstring>> outLines;
      optimizeStartOrder(outLines, di, cInfo);
      for (auto &ol : outLines)
        gdi.addString("", ol.first, ol.second);

      int laststart = 0;
      for (size_t k = 0; k < cInfo.size(); k++) {
        const ClassInfo &ci = cInfo[k];
        laststart = max(laststart, ci.firstStart + ci.nRunners*ci.interval);
      }

      gdi.addStringUT(1, lang.tl("Sista start (nu tilldelad)") + L": " +
                      getAbsTime((laststart)*di.baseInterval + di.firstStart));
      gdi.dropLine();
      gdi.refreshFast();

      for (size_t k = 0; k < cInfo.size(); k++) {
        const ClassInfo &ci = cInfo[k];

        if (getClass(ci.classId)->getClassType() == oClassRelay) {
          gdi.addString("", 0, L"Hoppar över stafettklass: X#" +
                        getClass(ci.classId)->getName()).setColor(colorRed);
          continue;
        }

        gdi.addString("", 0, L"Lottar: X#" + getClass(ci.classId)->getName());
        vector<ClassDrawSpecification> spec;
        spec.emplace_back(ci.classId, leg,
                          di.firstStart + di.baseInterval * ci.firstStart,
                          di.baseInterval * ci.interval, ci.nVacant, vp);

        drawList(spec, method, pairSize, DrawType::DrawAll);
        gdi.scrollToBottom();
        gdi.refreshFast();
        drawn++;
      }
    }
    else {
      vector<ClassDrawSpecification> spec;
      for (oClassList::iterator it = Classes.begin(); it != Classes.end(); ++it) {
        if (it->getStart() != start)
          continue;
        if (notDrawn.count(it->getId()) == 0)
          continue; // Only not drawn classes
        if (it->hasFreeStart() || it->hasRequestStart())
          continue;
        //int classID, int leg, int firstStart, int interval, int vacances, oEvent::VacantPosition vp)
        spec.emplace_back(it->getId(), 0, 0, 120, 1, VacantPosition::Mixed);
        drawn++;
      }

      if (spec.size() == 0)
        continue;
      try {
        drawListStartGroups(spec, method, pairSize, DrawType::DrawAll);
      }
      catch (meosException &ex) {
        gdi.addString("", 1, ex.wwhat()).setColor(colorRed);
        // Relay classes?
        gdi.dropLine();
        gdi.refreshFast();
        return;
      }
    }
  }

  // Classes that need completion
  for (oClassList::iterator it = Classes.begin(); it!=Classes.end(); ++it) {
    if (needsCompletion.count(it->getId())==0)
      continue;
    if (it->hasFreeStart() || it->hasRequestStart())
      continue;

    gdi.addStringUT(0, lang.tl(L"Lottar efteranmälda: ") + it->getName());

    vector<ClassDrawSpecification> spec;
    spec.emplace_back(it->getId(), leg, 0, 0, 0, vp);
    drawList(spec, method, 1, lateBefore ? DrawType::RemainingBefore : DrawType::RemainingAfter);

    gdi.scrollToBottom();
    gdi.refreshFast();
    drawn++;
  }

  gdi.dropLine();

  if (drawn==0)
    gdi.addString("", 1, "Klart: inga klasser behövde lottas.").setColor(colorGreen);
  else
    gdi.addString("", 1, "Klart: alla klasser lottade.").setColor(colorGreen);
  // Relay classes?
  gdi.dropLine();
  gdi.refreshFast();
}

void oEvent::drawPersuitList(int classId, int firstTime, int restartTime,
                             int maxTime, int interval, 
                             int pairSize, bool reverse, double scale) {
  if (classId<=0)
    return;

  pClass pc=getClass(classId);

  if (!pc)
    throw std::exception("Klass saknas");

  const int leg = 0;
  if (size_t(leg) < pc->legInfo.size()) {
    pc->legInfo[leg].startMethod = STDrawn; //Automatically change start method
  }

  vector<pRunner> trunner;
  getRunners(classId, 0, trunner);

  vector<pRunner> runner;
  runner.reserve(trunner.size());
  for (size_t k = 0; k< trunner.size(); k++) // Only treat specified leg
    if (trunner[k]->tLeg == leg)
      runner.push_back(trunner[k]);

  if (runner.empty())
    return;

  // Make sure patrol members use the same time
  vector<int> adjustedTimes(runner.size());
  for (size_t k = 0; k<runner.size(); k++) {
    if (runner[k]->inputStatus == StatusOK && runner[k]->inputTime>0) {
      int it = runner[k]->inputTime;
      if (runner[k]->tInTeam) {
        for (size_t j = 0; j < runner[k]->tInTeam->Runners.size(); j++) {
          int it2 = runner[k]->tInTeam->Runners[j]->inputTime;
          if (it2 > 0)
            it = max(it, it2);
        }
      }
      adjustedTimes[k] = it;
    }
  }

  vector< pair<int, int> > times(runner.size());

  for (size_t k = 0; k<runner.size(); k++) {
    times[k].second = k;
    if (runner[k]->inputStatus == StatusOK && adjustedTimes[k]>0) {
      if (scale != 1.0)
        times[k].first = int(floor(double(adjustedTimes[k]) * scale + 0.5));
      else
        times[k].first = adjustedTimes[k];
    }
    else {
      times[k].first = timeConstHour * 24 * 7 + runner[k]->inputStatus;
      if (runner[k]->isVacant())
        times[k].first += 10; // Vacansies last
    }
  }
  // Sorted by name in input
  stable_sort(times.begin(), times.end());

  int delta = times[0].first;

  if (delta >= timeConstHour*24*7)
    delta = 0;

  int reverseDelta = 0;
  if (reverse) {
    for (size_t k = 0; k<times.size(); k++) {
      if ((times[k].first - delta) < maxTime)
        reverseDelta = times[k].first;
    }
  }
  int odd = 0;
  int breakIndex = -1;
  for (size_t k = 0; k<times.size(); k++) {
    pRunner r = runner[times[k].second];

    if ((times[k].first - delta) < maxTime && breakIndex == -1) {
      if (!reverse)
        r->setStartTime(firstTime + times[k].first - delta, true, ChangeType::Update, false);
      else
        r->setStartTime(firstTime - times[k].first + reverseDelta, true, ChangeType::Update, false);
    }
    else if (!reverse) {
      if (breakIndex == -1)
        breakIndex = k;

      r->setStartTime(restartTime + ((k - breakIndex)/pairSize) * interval, true, ChangeType::Update, false);
    }
    else {
      if (breakIndex == -1) {
        breakIndex = times.size() - 1;
        odd = times.size() % 2;
      }

      r->setStartTime(restartTime + ((breakIndex - k + odd)/pairSize) * interval, true, ChangeType::Update, false);
    }
    r->synchronize(true);
  }
  reCalculateLeaderTimes(classId);
}

int oEvent::requestStartTime(int runnerId, int afterThisTime, int minTimeInterval,
                             int lastAllowedTime, int maxParallel,
                             bool allowSameCourse, bool allowSameCourseNeighbour, 
                             bool allowSameFirstControl, bool allowClubNeighbour) {
  synchronizeList({ oListId::oLRunnerId, oListId::oLClassId, oListId::oLCourseId });
  pRunner runner = getRunner(runnerId, 0);
  if (!runner || !runner->Class)
    return -1;
  
  assert(minTimeInterval > 0);
  pCourse crs = runner->getCourse(false);
  pControl firstC = !allowSameFirstControl && crs ? crs->getControl(0) : nullptr;
  wstring start = crs ? crs->getStart() : L"";

  set<int> usedStart;
  set<int> usedStartClass;
  set<int> usedClassClub;
  map<int, int> countStartSlots;
  int timeDivider = std::min(minTimeInterval, timeConstMinute);
  int slotDist = minTimeInterval / timeDivider;
  int closestStartSlotClass = numeric_limits<int>::max();
  int desiredSlot = afterThisTime / timeDivider;

  for (const oRunner &r : Runners) {
    if (r.isRemoved() || r.getStatus() == StatusNotCompetiting || r.getStatus() == StatusCANCEL)
      continue;

    if (r.getStartTime() <= 0)
      continue;

    // If needs to be inserted in map, otherwise just compute closest
    bool neededInMap = r.getStartTime() >= afterThisTime - timeDivider * slotDist;
     
    pCourse otherCrs = r.getCourse(false);
    int slot = r.getStartTime() / timeDivider;
    if (maxParallel > 0 && (!otherCrs || otherCrs->getStart() == start))
      if (neededInMap)
        ++countStartSlots[slot];

    if (r.Class == runner->Class) {
      if (abs(slot - desiredSlot) < abs(closestStartSlotClass-desiredSlot))
        closestStartSlotClass = slot;
      if (neededInMap) {
        usedStart.insert(slot);
        usedStartClass.insert(slot);
        if (!allowClubNeighbour && r.Club == runner->Club && r.Club && r.getClubId() != oe->getVacantClubIfExist(true)) {
          usedClassClub.insert(slot);
        }
      }
    }
    else if (!allowSameCourse && otherCrs == crs) {
      usedStart.insert(slot);
      if (allowSameCourseNeighbour) {
        if (abs(slot - slotDist) < abs(closestStartSlotClass - desiredSlot))
          closestStartSlotClass = slot;

        if (neededInMap)
          usedStartClass.insert(slot);
      }
    }
    else if (!allowSameFirstControl && otherCrs && otherCrs->getControl(0) == firstC) {
      if (neededInMap)
        usedStart.insert(slot);
    }
  }

  if (closestStartSlotClass < 10000) {
    if (closestStartSlotClass > desiredSlot) {
      int off = (closestStartSlotClass - desiredSlot) % slotDist;
      if (off > 0) {
        afterThisTime += off * timeDivider;
      }
    }
    else if (closestStartSlotClass < desiredSlot) {
      int off = (desiredSlot - closestStartSlotClass) % slotDist;
      if (off > 0) {
        int slotAdjust = slotDist - off;
        afterThisTime += slotAdjust * timeDivider;
      }
    }
  }
  for (int startTime = afterThisTime; startTime <= lastAllowedTime; startTime += timeDivider) {
    int slot = startTime / timeDivider;
    if (usedStart.count(slot))
      continue;
    if (maxParallel > 0) {
      auto res = countStartSlots.find(slot);
      if (res != countStartSlots.end() && res->second >= maxParallel)
        continue;
    }

    if (usedClassClub.count(slot - slotDist)) {
      startTime += (slotDist - 1) * timeDivider;
      continue;
    }
    if (usedClassClub.count(slot + slotDist)) {
      startTime += (2 * slotDist - 1) * timeDivider;
      continue;
    }

    bool ok = true;
    for (int j = 1; j < slotDist; j++) {
      if (usedStartClass.count(slot + j)) {
        ok = false;
        startTime += (j + slotDist - 1) * timeDivider; // Jump forward (optimize)
        break;
      }
      if (usedStartClass.count(slot - j)) {
        ok = false;
        break;
      }
    }
    if (ok) 
      return startTime;
  }
  return -1; // No time found
}
