/************************************************************************
MeOS - Orienteering Software
Copyright (C) 2009-2023 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 for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Melin Software HB - software@melin.nu - www.melin.nu
Eksoppsvägen 16, SE-75646 UPPSALA, Sweden
************************************************************************/
// oClass.cpp: implementation of the oClass class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#define DODECLARETYPESYMBOLS
#include
#include "oClass.h"
#include "oEvent.h"
#include "Table.h"
#include "meos_util.h"
#include
#include "Localizer.h"
#include
#include "inthashmap.h"
#include "intkeymapimpl.hpp"
#include "SportIdent.h"
#include "MeOSFeatures.h"
#include "gdioutput.h"
#include "gdistructures.h"
#include "meosexception.h"
#include "random.h"
#include "qualification_final.h"
#include "generalresult.h"
#include "metalist.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
oClass::oClass(oEvent *poe): oBase(poe)
{
getDI().initData();
Course=0;
Id=oe->getFreeClassId();
tLeaderTime.resize(1);
tNoTiming = -1;
tIgnoreStartPunch = -1;
tLegTimeToPlace = 0;
tLegAccTimeToPlace = 0;
tSplitRevision = 0;
tSortIndex = 0;
tMaxTime = 0;
tCoursesChanged = false;
tStatusRevision = 0;
tShowMultiDialog = false;
parentClass = 0;
}
oClass::oClass(oEvent *poe, int id): oBase(poe)
{
getDI().initData();
Course=0;
if (id == 0)
id = oe->getFreeClassId();
Id=id;
oe->qFreeClassId = max(id % MaxClassId, oe->qFreeClassId);
tLeaderTime.resize(1);
tNoTiming = -1;
tIgnoreStartPunch = -1;
tLegTimeToPlace = 0;
tLegAccTimeToPlace = 0;
tSplitRevision = 0;
tSortIndex = 0;
tMaxTime = 0;
tCoursesChanged = false;
tStatusRevision = 0;
tShowMultiDialog = false;
parentClass = 0;
}
void oClass::clearDuplicate() {
int id = oe->getFreeClassId();
clearDuplicateBase(id);
oe->qFreeClassId = max(id % MaxClassId, oe->qFreeClassId);
getDI().setInt("SortIndex", tSortIndex);
}
oClass::~oClass()
{
if (tLegTimeToPlace)
delete tLegTimeToPlace;
if (tLegAccTimeToPlace)
delete tLegAccTimeToPlace;
}
bool oClass::Write(xmlparser &xml)
{
if (Removed) return true;
xml.startTag("Class");
xml.write("Id", Id);
xml.write("Updated", getStamp());
xml.write("Name", Name);
if (Course)
xml.write("Course", Course->Id);
if (MultiCourse.size()>0)
xml.write("MultiCourse", codeMultiCourse());
if (legInfo.size()>0)
xml.write("LegMethod", codeLegMethod());
getDI().write(xml);
xml.endTag();
return true;
}
void oClass::Set(const xmlobject *xo)
{
xmlList xl;
xo->getObjects(xl);
xmlList::const_iterator it;
for(it=xl.begin(); it != xl.end(); ++it){
if (it->is("Id")){
Id = it->getInt();
}
else if (it->is("Name")){
Name = it->getWStr();
if (Name.size() > 1 && Name.at(0) == '%') {
Name = lang.tl(Name.substr(1));
}
}
else if (it->is("Course")){
Course = oe->getCourse(it->getInt());
}
else if (it->is("MultiCourse")){
set cid;
vector< vector > multi;
parseCourses(it->getRawStr(), multi, cid);
importCourses(multi);
}
else if (it->is("LegMethod")){
importLegMethod(it->getRawStr());
}
else if (it->is("oData")){
getDI().set(*it);
}
else if (it->is("Updated")){
Modified.setStamp(it->getRawStr());
}
}
// Reinit temporary data
getNoTiming();
}
void oClass::importCourses(const vector< vector > &multi)
{
MultiCourse.resize(multi.size());
for (size_t k=0;kgetCourse(multi[k][j]);
}
}
setNumStages(MultiCourse.size());
}
set &oClass::getMCourseIdSet(set &in) const
{
in.clear();
for (size_t k=0;kgetId());
}
}
return in;
}
string oClass::codeMultiCourse() const
{
vector< vector >::const_iterator stage_it;
string str;
char bf[16];
for (stage_it=MultiCourse.begin();stage_it!=MultiCourse.end(); ++stage_it) {
vector::const_iterator it;
for (it=stage_it->begin();it!=stage_it->end(); ++it) {
if (*it){
sprintf_s(bf, 16, " %d", (*it)->getId());
str+=bf;
}
else str+=" 0";
}
str += ";";
}
if (str.length() == 1)
return "@"; // Special code for the case of one stage and no course
else if (str.length()>0) {
return trim(str.substr(0, str.length()-1));
}
//if (str.length()>0)
// return trim(str);
else return "";
}
void oClass::parseCourses(const string &courses,
vector< vector > &multi,
set &courseId)
{
courseId.clear();
multi.clear();
if (courses.empty())
return;
const char *str=courses.c_str();
vector empty;
multi.push_back(empty);
int n_stage=0;
while (*str && isspace(*str))
str++;
while (*str) {
int cid=atoi(str);
if (cid) {
multi[n_stage].push_back(cid);
courseId.insert(cid);
}
while (*str && (*str!=';' && *str!=' ')) str++;
if (*str==';') {
str++;
while (*str && *str==' ') str++;
n_stage++;
multi.push_back(empty);
}
else {
if (*str) str++;
}
}
}
string oLegInfo::codeLegMethod() const {
char bsd[16], bret[16], brot[16];
auto codeTime = [](int t, char *b) -> const char * {
if (timeConstSecond == 1 || t <= 0)
sprintf_s(b, 16, "%d", t);
else
sprintf_s(b, 16, "%d.%d", (t / timeConstSecond),(t % timeConstSecond));
return b;
};
char bf[256];
if (isStartDataTime()) {
sprintf_s(bf, "(%s:%s:%s:%s:%s:%d)", StartTypeNames[startMethod],
LegTypeNames[legMethod],
codeTime(legStartData, bsd),
codeTime(legRestartTime, bret),
codeTime(legRopeTime, brot),
duplicateRunner);
}
else {
sprintf_s(bf, "(%s:%s:%d:%s:%s:%d)", StartTypeNames[startMethod],
LegTypeNames[legMethod],
legStartData,
codeTime(legRestartTime, bret),
codeTime(legRopeTime, brot),
duplicateRunner);
}
return bf;
}
void oLegInfo::importLegMethod(const string &leg) {
//Defaults
startMethod = STTime;
legMethod = LTNormal;
legStartData = 0;
legRestartTime = 0;
size_t begin = leg.find_first_of('(');
if (begin == string::npos)
return;
begin++;
string coreLeg = leg.substr(begin, leg.find_first_of(')') - begin);
vector legsplit;
split(coreLeg, ":", legsplit);
if (legsplit.size() >= 1) {
for (int st = 0; st < nStartTypes; ++st) {
if (legsplit[0] == StartTypeNames[st]) {
startMethod = (StartTypes)st;
break;
}
}
}
if (legsplit.size() >= 2) {
for (int t = 0; t < nLegTypes; ++t) {
if (legsplit[1] == LegTypeNames[t]) {
legMethod = (LegTypes)t;
break;
}
}
}
if (legsplit.size() >= 3) {
if (isStartDataTime())
legStartData = parseRelativeTime(legsplit[2].c_str());
else
legStartData = atoi(legsplit[2].c_str());
}
if (legsplit.size() >= 4)
legRestartTime = parseRelativeTime(legsplit[3].c_str());
if (legsplit.size() >= 5)
legRopeTime = parseRelativeTime(legsplit[4].c_str());
if (legsplit.size() >= 6)
duplicateRunner = atoi(legsplit[5].c_str());
}
string oClass::codeLegMethod() const
{
string code;
for(size_t k=0;k0) code+="*";
code+=legInfo[k].codeLegMethod();
}
return code;
}
wstring oClass::getInfo() const
{
return L"Klass " + Name;
}
void oClass::importLegMethod(const string &legMethods)
{
vector< string > legsplit;
split(legMethods, "*", legsplit);
legInfo.clear();
for (size_t k=0;kdataRevision) {
for (auto &c : oe->Classes) {
c.tTypeKeyToRunnerCount.second.clear();
c.tTypeKeyToRunnerCount.first = oe->dataRevision;
}
}
string key = getCountTypeKey(checkFirstLeg ? 0 : -1,
noCountNotCompeting ? CountKeyType::All : CountKeyType::IncludeNotCompeting,
!noCountVacant);
auto res = tTypeKeyToRunnerCount.second.find(key);
if (res != tTypeKeyToRunnerCount.second.end())
return res->second;
unordered_map nRunners;
for (auto &r : oe->Runners) {
if (r.isRemoved() || !r.Class)
continue;
if (checkFirstLeg && (r.tLeg > 0 && !r.Class->isQualificationFinalBaseClass()))
continue;
if (noCountVacant && r.isVacant())
continue;
if (noCountNotCompeting && r.getStatus() == StatusNotCompetiting)
continue;
int id = r.getClassId(true);
++nRunners[id];
}
for (auto &c : oe->Classes) {
if (!c.isRemoved())
c.tTypeKeyToRunnerCount.second[key] = nRunners[c.Id];
}
return nRunners[Id];
}
void oClass::getNumResults(int leg, int &total, int &finished, int &dns) const {
if (tTypeKeyToRunnerCount.first != oe->dataRevision) {
for (auto &c : oe->Classes) {
c.tTypeKeyToRunnerCount.second.clear();
c.tTypeKeyToRunnerCount.first = oe->dataRevision;
}
}
string keyTot = getCountTypeKey(leg, CountKeyType::ExpectedStarting, false);
string keyFinished = getCountTypeKey(leg, CountKeyType::Finished, false);
string keyDNS = getCountTypeKey(leg, CountKeyType::DNS, false);
auto rTot = tTypeKeyToRunnerCount.second.find(keyTot);
auto rFinished = tTypeKeyToRunnerCount.second.find(keyFinished);
auto rDNS = tTypeKeyToRunnerCount.second.find(keyDNS);
if (rTot != tTypeKeyToRunnerCount.second.end() &&
rFinished != tTypeKeyToRunnerCount.second.end() &&
rDNS != tTypeKeyToRunnerCount.second.end()) {
total = rTot->second;
finished = rFinished->second;
dns = rDNS->second;
return;
}
struct Cnt {
bool team = false;
bool singleClass = false;
int maxleg = 0;
int total = 0;
int finished = 0;
int dns = 0;
};
//Search runners
unordered_map cnt;
for (auto &c : oe->Classes) {
if (c.isRemoved())
continue;
ClassType ct = c.getClassType();
auto &cc = cnt[c.Id];
cc.maxleg = c.getLastStageIndex();
if (ct == oClassKnockout)
cc.singleClass = true || cc.maxleg == 1;
if (!(ct == oClassIndividual || ct == oClassIndividRelay || ct == oClassKnockout))
cnt[c.Id].team = true;
}
for (auto &r : oe->Runners) {
if (r.isRemoved() || !r.Class || r.tStatus == StatusNotCompetiting || r.tStatus == StatusCANCEL)
continue;
auto &c = cnt[r.getClassId(true)];
if (c.team)
continue;
int tleg = leg > 0 ? leg : c.maxleg;
if (r.tLeg == tleg || c.singleClass) {
c.total++;
if (r.tStatus != StatusUnknown)
c.finished++;
if (r.tStatus == StatusDNS)
c.dns++;
}
}
for (auto &t : oe->Teams) {
if (t.isRemoved() || !t.Class || t.tStatus == StatusNotCompetiting || t.tStatus == StatusCANCEL)
continue;
auto &c = cnt[t.getClassId(true)];
if (!c.team)
continue;
c.total++;
if (t.tStatus != StatusUnknown || t.getLegStatus(leg, false, false) != StatusUnknown)
c.finished++;
}
for (auto &c : oe->Classes) {
auto &cc = cnt[c.Id];
c.tTypeKeyToRunnerCount.second[keyDNS] = cc.dns;
c.tTypeKeyToRunnerCount.second[keyFinished] = cc.finished;
c.tTypeKeyToRunnerCount.second[keyTot] = cc.total;
}
auto &cc = cnt[Id];
dns = cc.dns;
total = cc.total;
finished = cc.finished;
}
void oClass::setCourse(pCourse c)
{
if (Course!=c){
if (MultiCourse.size() == 1) {
// MultiCourse wich is in fact only one course, (e.g. for fixed start time in class). Keep in synch.
if (c != 0) {
if (MultiCourse[0].size() == 1)
MultiCourse[0][0] = c;
else if (MultiCourse[0].size() == 0)
MultiCourse[0].push_back(c);
}
else {
if (MultiCourse[0].size() == 1)
MultiCourse[0].pop_back();
}
}
Course=c;
tCoursesChanged = true;
updateChanged();
// Update start from course
if (Course && !Course->getStart().empty()) {
setStart(Course->getStart());
}
}
}
void oClass::setName(const wstring &name, bool manualSet)
{
if (getName() != name) {
Name = name;
if (manualSet)
setFlag(TransferFlags::FlagManualName, true);
updateChanged();
}
}
oDataContainer &oClass::getDataBuffers(pvoid &data, pvoid &olddata, pvectorstr &strData) const {
data = (pvoid)oData;
olddata = (pvoid)oDataOld;
strData = const_cast< vector >* >(&oDataStr);
return *oe->oClassData;
}
int oEvent::getNumClasses() const {
int nc = 0;
for (auto& c : Classes) {
if (!c.isRemoved())
nc++;
}
return nc;
}
pClass oEvent::getClassCreate(int Id, const wstring &createName, set &exactMatch) {
if (Id>0) {
oClassList::iterator it;
for (it=Classes.begin(); it != Classes.end(); ++it) {
if (it->Id==Id && !it->isRemoved()) {
if (compareClassName(createName, it->getName())) {
if (it!=Classes.begin())
Classes.splice(Classes.begin(), Classes, it, Classes.end());
return &Classes.front();
}
else {
Id=0; //Bad Id
break;
}
}
}
}
if (createName.empty() && Id>0) {
oClass c(this, Id);
c.setName(getAutoClassName(), false);
return addClass(c);
}
else {
bool exact = exactMatch.count(createName) > 0;
//Check if class exist under different id
for (auto &c : Classes) {
if (c.isRemoved())
continue;
if (!exact && exactMatch.count(c.Name) == 0 && compareClassName(c.Name, createName)) {
return &c;
}
if (exact && c.Name == createName) {
return &c;
}
}
if (Id<=0)
Id=getFreeClassId();
oClass c(this, Id);
c.Name = createName;
exactMatch.insert(createName);
return addClass(c);
}
}
bool oEvent::getClassesFromBirthYear(int year, PersonSex sex, vector &classes) const {
classes.clear();
int age = year>0 ? getThisYear() - year : 0;
int bestMatchClass = -1;
int bestMatchDist = 1000;
for (oClassList::const_iterator it=Classes.begin(); it != Classes.end(); ++it) {
if (it->isRemoved())
continue;
if (it->getClassType() == ClassType::oClassRelay)
continue;
PersonSex clsSex = it->getSex();
if (clsSex == sFemale && sex == sMale)
continue;
if (clsSex == sMale && sex == sFemale)
continue;
int distance = 1000;
if (age>0) {
int high, low;
it->getAgeLimit(low, high);
if (high>0 && age>high)
continue;
if (low>0 && age0)
distance = high - age;
if (low>0)
distance = min(distance, age-low);
if (distance < bestMatchDist) {
if (bestMatchClass != -1)
classes.push_back(bestMatchClass);
// Add best class last
bestMatchClass = it->getId();
bestMatchDist = distance;
}
else
classes.push_back(it->getId());
}
else
classes.push_back(it->getId());
}
// Add best class last
if (bestMatchClass != -1) {
classes.push_back(bestMatchClass);
return true;
}
return false;
}
static bool clsSortFunction (pClass i, pClass j) {
return (*i < *j);
}
void oEvent::getClasses(vector &classes, bool sync) const {
if (sync) {
oe->synchronizeList(oListId::oLCourseId);
oe->reinitializeClasses();
}
classes.clear();
for (oClassList::const_iterator it = Classes.begin(); it != Classes.end(); ++it) {
if (it->isRemoved())
continue;
classes.push_back(pClass(&*it));
}
sort(classes.begin(), classes.end(), clsSortFunction);
}
pClass oEvent::getBestClassMatch(const wstring &cname) const {
return getClass(cname);
}
pClass oEvent::getClass(const wstring &cname) const
{
for (oClassList::const_iterator it=Classes.begin(); it != Classes.end(); ++it) {
if (!it->isRemoved() && compareClassName(cname, it->Name))
return pClass(&*it);
}
return 0;
}
pClass oEvent::getClass(int Id) const {
if (Id<=0)
return nullptr;
oClassList::const_iterator it;
for (it=Classes.begin(); it != Classes.end(); ++it){
if (it->Id==Id && !it->isRemoved()) {
return pClass(&*it);
}
}
return nullptr;
}
pClass oEvent::addClass(const wstring &pname, int CourseId, int classId)
{
if (classId > 0){
pClass pOld=getClass(classId);
if (pOld)
return 0;
}
oClass c(this, classId);
c.Name=pname;
if (CourseId>0)
c.Course=getCourse(CourseId);
Classes.push_back(c);
Classes.back().addToEvent(this, &c);
Classes.back().synchronize();
updateTabs();
return &Classes.back();
}
pClass oEvent::addClass(const oClass &c)
{
if (c.Id==0)
return 0;
else {
pClass pOld=getClass(c.getId());
if (pOld)
return 0;
}
Classes.push_back(c);
Classes.back().addToEvent(this, &c);
if (hasDBConnection() && !Classes.back().existInDB() && !c.isImplicitlyCreated()) {
Classes.back().changed = true;
Classes.back().synchronize();
}
return &Classes.back();
}
bool oClass::fillStageCourses(gdioutput &gdi, int stage,
const string &name) const
{
if (unsigned(stage)>=MultiCourse.size())
return false;
gdi.clearList(name);
const vector &Stage=MultiCourse[stage];
vector::const_iterator it;
string out;
string str="";
wchar_t bf[128];
int m=0;
for (it=Stage.begin(); it!=Stage.end(); ++it) {
swprintf_s(bf, L"%d: %s", ++m, (*it)->getName().c_str());
gdi.addItem(name, bf, (*it)->getId());
}
return true;
}
bool oClass::addStageCourse(int iStage, int courseId, int index)
{
return addStageCourse(iStage, oe->getCourse(courseId), index);
}
bool oClass::addStageCourse(int iStage, pCourse pc, int index)
{
if (unsigned(iStage)>=MultiCourse.size())
return false;
vector &stage=MultiCourse[iStage];
if (pc) {
tCoursesChanged = true;
if (index == -1 || size_t(index) >= stage.size())
stage.push_back(pc);
else {
stage.insert(stage.begin() + index, pc);
}
updateChanged();
return true;
}
return false;
}
bool oClass::moveStageCourse(int stage, int index, int offset) {
if (unsigned(stage) >= MultiCourse.size())
return false;
vector &stages = MultiCourse[stage];
if (offset == -1 && size_t(index) < stages.size() && index > 0) {
swap(stages[index - 1], stages[index]);
updateChanged();
return true;
}
else if (offset == 1 && size_t(index + 1) < stages.size() && index >= 0) {
swap(stages[index + 1], stages[index]);
updateChanged();
return true;
}
return false;
}
void oClass::clearStageCourses(int stage) {
if (size_t(stage) < MultiCourse.size())
MultiCourse[stage].clear();
}
bool oClass::removeStageCourse(int iStage, int CourseId, int position)
{
if (unsigned(iStage)>=MultiCourse.size())
return false;
vector &Stage=MultiCourse[iStage];
if ( !(DWORD(position)getId()==CourseId){
tCoursesChanged = true;
Stage.erase(Stage.begin()+position);
updateChanged();
return true;
}
return false;
}
void oClass::setNumStages(int no)
{
if (no>=0) {
if (MultiCourse.size() != no)
updateChanged();
MultiCourse.resize(no);
legInfo.resize(no);
tLeaderTime.resize(max(no, 1));
}
oe->updateTabs();
}
void oClass::getTrueStages(vector &stages) const
{
stages.clear();
if (!legInfo.empty()) {
for (size_t k = 0; k+1 < legInfo.size(); k++) {
if (legInfo[k].trueLeg != legInfo[k+1].trueLeg) {
stages.push_back(TrueLegInfo(k, legInfo[k].trueLeg));
}
}
stages.push_back(TrueLegInfo(legInfo.size()-1, legInfo.back().trueLeg));
for (size_t k = 0; k 0 ? stages[k-1].first + 1: 0;
while(stages[k].nonOptional <= stages[k].first) {
if (!legInfo[stages[k].nonOptional].isOptional())
break;
else
stages[k].nonOptional++;
}
}
}
else {
stages.push_back(TrueLegInfo(0,1));
stages.back().nonOptional = -1;
}
}
bool oClass::startdataIgnored(int i) const
{
StartTypes st=getStartType(i);
LegTypes lt=getLegType(i);
if (lt==LTIgnore || lt==LTExtra || lt==LTParallel || lt == LTParallelOptional)
return true;
if (st==STChange || st==STDrawn)
return true;
return false;
}
bool oClass::restartIgnored(int i) const
{
StartTypes st=getStartType(i);
LegTypes lt=getLegType(i);
if (lt==LTIgnore || lt==LTExtra || lt==LTParallel || lt == LTParallelOptional || lt == LTGroup)
return true;
if (st==STTime || st==STDrawn)
return true;
return false;
}
void oClass::fillStartTypes(gdioutput &gdi, const string &name, bool firstLeg)
{
gdi.clearList(name);
gdi.addItem(name, lang.tl("Starttid"), STTime);
if (!firstLeg)
gdi.addItem(name, lang.tl("Växling"), STChange);
gdi.addItem(name, lang.tl("Tilldelad"), STDrawn);
if (!firstLeg)
gdi.addItem(name, lang.tl("Jaktstart"), STPursuit);
}
StartTypes oClass::getStartType(int leg) const
{
leg = mapLeg(leg);
if (unsigned(leg) 0 && (isParallel(leg) || isOptional(leg)) )
return getRestartTime(leg-1);
if (unsigned(leg) 0 && (isParallel(leg) || isOptional(leg)) )
return getRopeTime(leg-1);
if (unsigned(leg)0)
return oe->getAbsTime(s);
else return makeDash(L"-");
}
else if (t==STChange || t==STDrawn)
return makeDash(L"-");
return L"?";
}
wstring oClass::getRestartTimeS(int leg) const
{
leg = mapLeg(leg);
int s=getRestartTime(leg);
StartTypes t=getStartType(leg);
if (t==STChange || t==STPursuit) {
if (s>0)
return oe->getAbsTime(s);
else return makeDash(L"-");
}
else if (t==STTime || t==STDrawn)
return makeDash(L"-");
return L"?";
}
wstring oClass::getRopeTimeS(int leg) const
{
leg = mapLeg(leg);
int s=getRopeTime(leg);
StartTypes t=getStartType(leg);
if (t==STChange || t==STPursuit) {
if (s>0)
return oe->getAbsTime(s);
else return makeDash(L"-");
}
else if (t==STTime || t==STDrawn)
return makeDash(L"-");
return L"?";
}
int oClass::getLegRunner(int leg) const {
leg = mapLeg(leg);
if (unsigned(leg)=0) {
changed=true;
legInfo.resize(leg+1);
}
legInfo[leg].duplicateRunner=runnerNo;
if (changed)
updateChanged();
}
bool oClass::checkStartMethod() {
StartTypes st = STTime;
bool error = false;
for (size_t j = 0; j < legInfo.size(); j++) {
if (!legInfo[j].isParallel())
st = legInfo[j].startMethod;
else if ((legInfo[j].startMethod == STChange || legInfo[j].startMethod == STPursuit) && st != legInfo[j].startMethod) {
legInfo[j].startMethod = STDrawn;
error = true;
}
}
return error;
}
void oClass::setStartType(int leg, StartTypes st, bool throwError)
{
bool changed=false;
if (unsigned(leg)=0) {
changed=true;
legInfo.resize(leg+1);
}
legInfo[leg].startMethod=st;
bool error = checkStartMethod();
if (changed || error)
updateChanged();
if (error && throwError) {
throw meosException("Ogiltig startmetod på sträcka X#" + itos(leg+1));
}
}
void oClass::setLegType(int leg, LegTypes lt)
{
bool changed=false;
if (unsigned(leg)=0) {
changed=true;
legInfo.resize(leg+1);
}
legInfo[leg].legMethod=lt;
bool error = checkStartMethod();
if (changed || error) {
apply();
updateChanged();
}
if (error) {
throw meosException("Ogiltig startmetod på sträcka X#" + itos(leg+1));
}
}
bool oClass::setStartData(int leg, const wstring &s) {
int rt;
StartTypes styp=getStartType(leg);
if (styp==STTime || styp==STPursuit)
rt=oe->getRelativeTime(s);
else
rt=_wtoi(s.c_str());
return setStartData(leg, rt);
}
bool oClass::setStartData(int leg, int value) {
bool changed = false;
if (unsigned(leg) < legInfo.size())
changed = legInfo[leg].legStartData != value;
else if (leg >= 0) {
changed = true;
legInfo.resize(leg + 1);
}
legInfo[leg].legStartData = value;
if (changed)
updateChanged();
return changed;
}
void oClass::setRestartTime(int leg, const wstring &t)
{
int rt=oe->getRelativeTime(t);
bool changed=false;
if (unsigned(leg)=0) {
changed=true;
legInfo.resize(leg+1);
}
legInfo[leg].legRestartTime=rt;
if (changed)
updateChanged();
}
void oClass::setRopeTime(int leg, const wstring &t)
{
int rt=oe->getRelativeTime(t);
bool changed=false;
if (unsigned(leg)=0) {
changed=true;
legInfo.resize(leg+1);
}
legInfo[leg].legRopeTime=rt;
if (changed)
updateChanged();
}
void oClass::fillLegTypes(gdioutput &gdi, const string &name)
{
vector< pair > types;
types.push_back( make_pair(lang.tl("Normal"), LTNormal));
types.push_back( make_pair(lang.tl("Parallell"), LTParallel));
types.push_back( make_pair(lang.tl("Valbar"), LTParallelOptional));
types.push_back( make_pair(lang.tl("Extra"), LTExtra));
types.push_back( make_pair(lang.tl("Summera"), LTSum));
types.push_back( make_pair(lang.tl("Medlöpare"), LTIgnore));
types.push_back( make_pair(lang.tl("Gruppera"), LTGroup));
gdi.addItem(name, types);
}
void oEvent::fillClasses(gdioutput &gdi, const string &id, ClassExtra extended, ClassFilter filter)
{
vector< pair > d;
oe->fillClasses(d, extended, filter);
gdi.addItem(id, d);
}
const vector< pair > &oEvent::fillClasses(vector< pair > &out,
ClassExtra extended, ClassFilter filter)
{
set undrawn;
set hasRunner;
out.clear();
if (extended == extraDrawn) {
oRunnerList::iterator rit;
for (rit=Runners.begin(); rit != Runners.end(); ++rit) {
bool needTime = true;
if (rit->isRemoved())
continue;
pClass pc = rit->getClassRef(true);
if (pc) {
if (pc->getNumStages() > 0 && pc->getStartType(rit->tLeg) != STDrawn)
needTime = false;
}
if (rit->tStartTime==0 && needTime)
undrawn.insert(rit->getClassId(true));
hasRunner.insert(rit->getClassId(true));
}
}
else if (extended == extraNumMaps)
calculateNumRemainingMaps(false);
oClassList::iterator it;
synchronizeList(oListId::oLClassId);
reinitializeClasses();
Classes.sort();//Sort by Id
for (it=Classes.begin(); it != Classes.end(); ++it){
if (!it->Removed) {
if (filter==filterOnlyMulti && it->getNumStages()<=1)
continue;
else if (filter==filterOnlySingle && it->getNumStages()>1)
continue;
else if (filter==filterOnlyDirect && !it->getAllowQuickEntry())
continue;
if (extended == extraNone)
out.push_back(make_pair(it->Name, it->Id));
//gdi.addItem(name, it->Name, it->Id);
else if (extended == extraDrawn) {
wchar_t bf[256];
if (it->MultiCourse.size() > 0 && it->getStartType(0) == STTime)
swprintf_s(bf, L"%s\t%s", it->Name.c_str(), it->getStartDataS(0).c_str());
else if (undrawn.count(it->getId()) || !hasRunner.count(it->getId()))
swprintf_s(bf, L"%s", it->Name.c_str());
else {
swprintf_s(bf, L"%s\t[S]", it->Name.c_str());
}
out.push_back(make_pair(wstring(bf), it->Id));
//gdi.addItem(name, bf, it->Id);
}
else if (extended == extraNumMaps) {
wchar_t bf[256];
int nmaps = it->getNumRemainingMaps(false);
if (nmaps != numeric_limits::min())
swprintf_s(bf, L"%s (%d %s)", it->Name.c_str(), nmaps, lang.tl(L"kartor").c_str());
else
swprintf_s(bf, L"%s ( - %s)", it->Name.c_str(), lang.tl(L"kartor").c_str());
out.push_back(make_pair(wstring(bf), it->Id));
}
}
}
return out;
}
void oEvent::getNotDrawnClasses(set &classes, bool someMissing)
{
set drawn;
classes.clear();
oRunnerList::iterator rit;
synchronizeList(oListId::oLRunnerId);
for (rit=Runners.begin(); rit != Runners.end(); ++rit) {
if (rit->tStartTime>0)
drawn.insert(rit->getClassId(true));
else if (someMissing)
classes.insert(rit->getClassId(true));
}
// Return all classe where some runner has no start time
if (someMissing)
return;
oClassList::iterator it;
synchronizeList(oListId::oLClassId);
// Return classes where no runner has a start time
for (it=Classes.begin(); it != Classes.end(); ++it) {
if (drawn.count(it->getId())==0)
classes.insert(it->getId());
}
}
void oEvent::getAllClasses(set &classes)
{
classes.clear();
oClassList::const_iterator it;
synchronizeList(oListId::oLClassId);
for (it=Classes.begin(); it != Classes.end(); ++it){
if (!it->Removed){
classes.insert(it->getId());
}
}
}
bool oEvent::fillClassesTB(gdioutput &gdi)//Table mode
{
oClassList::iterator it;
synchronizeList(oListId::oLClassId);
reinitializeClasses();
Classes.sort();//Sort by Id
int dx[4]={100, 100, 50, 100};
int y=gdi.getCY();
int x=gdi.getCX();
int lh=gdi.getLineHeight();
y+=lh/2;
int xp=x;
gdi.addString("", y, xp, 0, "Klass"); xp+=dx[0];
gdi.addString("", y, xp, 0, "Bana"); xp+=dx[1];
gdi.addString("", y, xp, 0, "Deltagare"); xp+=dx[2];
y+=(3*lh)/2;
for (it = Classes.begin(); it != Classes.end(); ++it){
if (!it->Removed){
int xp=x;
gdi.addString("", y, xp, 0, it->getName(), dx[0]); xp+=dx[0];
pCourse pc=it->getCourse();
if (pc) gdi.addString("", y, xp, 0, pc->getName(), dx[1]);
else gdi.addString("", y, xp, 0, "-", dx[1]);
xp+=dx[1];
char num[10];
_itoa_s(it->getNumRunners(false, false, false), num, 10);
gdi.addString("", y, xp, 0, num, dx[2]);
xp+=dx[2];
y+=lh;
}
}
return true;
}
bool oClass::isCourseUsed(int Id) const
{
if (Course && Course->getId()==Id)
return true;
if (hasMultiCourse()){
for(unsigned i=0;i &pv=MultiCourse[i];
for(unsigned j=0; jgetId()==Id) return true;
}
}
return false;
}
bool oClass::hasTrueMultiCourse() const {
if (MultiCourse.empty())
return false;
return MultiCourse.size()>1 || hasCoursePool() || tShowMultiDialog ||
(MultiCourse.size()==1 && MultiCourse[0].size()>1);
}
wstring oClass::getLength(int leg) const {
leg = mapLeg(leg);
wchar_t bf[64];
if (hasMultiCourse()){
int minlen=1000000;
int maxlen=0;
for(unsigned i=0;i &pv=MultiCourse[i];
for(unsigned j=0; jgetLength();
minlen=min(l, minlen);
maxlen=max(l, maxlen);
}
}
}
if (maxlen==0)
return _EmptyWString;
else if (minlen==0)
minlen=maxlen;
if ( (maxlen-minlen)<100 )
swprintf_s(bf, L"%d", maxlen);
else
swprintf_s(bf, L"%d - %d", minlen, maxlen);
return makeDash(bf);
}
else if (Course && Course->getLength()>0) {
return Course->getLengthS();
}
return _EmptyWString;
}
bool oClass::hasUnorderedLegs() const {
return getDCI().getInt("Unordered") != 0;
}
void oClass::setUnorderedLegs(bool order) {
getDI().setInt("Unordered", order);
}
void oClass::getParallelRange(int leg, int &parLegRangeMin, int &parLegRangeMax) const {
parLegRangeMin = leg;
while (parLegRangeMin > 0 && size_t(parLegRangeMin) < legInfo.size()) {
if (legInfo[parLegRangeMin].isParallel())
parLegRangeMin--;
else
break;
}
parLegRangeMax = leg;
while (size_t(parLegRangeMax+1) < legInfo.size()) {
if (legInfo[parLegRangeMax+1].isParallel() || legInfo[parLegRangeMax+1].isOptional())
parLegRangeMax++;
else
break;
}
}
void oClass::getParallelOptionalRange(int leg, int& parLegRangeMin, int& parLegRangeMax) const {
parLegRangeMin = leg;
while (parLegRangeMin > 0 && size_t(parLegRangeMin) < legInfo.size()) {
if (legInfo[parLegRangeMin].isParallel() || legInfo[parLegRangeMin].isOptional())
parLegRangeMin--;
else
break;
}
parLegRangeMax = leg;
while (size_t(parLegRangeMax + 1) < legInfo.size()) {
if (legInfo[parLegRangeMax + 1].isParallel() || legInfo[parLegRangeMax + 1].isOptional())
parLegRangeMax++;
else
break;
}
}
void oClass::getParallelCourseGroup(int leg, int startNo, vector< pair > &group) const {
group.clear();
// Assume hasUnorderedLegs
/*if (!hasUnorderedLegs()) {
pCourse crs = Course;
if (leg < MultiCourse.size()) {
int size = MultiCourse[leg].size();
if (size > 0) {
crs = MultiCourse[leg][startNo%leg];
}
}
group.push_back(make_pair(leg, crs));
return;
}
else*/ {
// Find first leg in group
while (leg > 0 && size_t(leg) < legInfo.size()) {
if (legInfo[leg].isParallel())
leg--;
else
break;
}
if (startNo <= 0)
startNo = 1; // Use first course
// Fill in all legs in the group
do {
if (size_t(leg) < MultiCourse.size()) {
int size = MultiCourse[leg].size();
if (size > 0) {
pCourse crs = MultiCourse[leg][(startNo-1)%size];
group.push_back(make_pair(leg, crs));
}
}
leg++;
}
while (size_t(leg) < legInfo.size() &&
legInfo[leg].isParallel());
}
}
pCourse oClass::selectParallelCourse(const oRunner &r, const SICard &sic) {
synchronize();
pCourse rc = 0; //Best match course
vector< pair > group;
getParallelCourseGroup(r.getLegNumber(), r.getStartNo(), group);
cTeam t = r.getTeam();
if (t && group.size() > 1) {
for (size_t k = 0; k < group.size(); k++) {
pRunner tr = t->getRunner(group[k].first);
if (!tr)
continue;
tr->synchronize();
if (tr->Course) {
// The course is assigned. Remove from group
for (size_t j = 0; j < group.size(); j++) {
if (group[j].second == tr->Course) {
group[j].second = 0;
break;
}
}
}
}
// Select best match of available courses
int distance=-1000;
for (size_t k = 0; k < group.size(); k++) {
if (group[k].second) {
int d = group[k].second->distance(sic);
if (d >= 0) {
if (distance < 0)
distance = 1000;
if (d distance) {
distance=d;
rc = group[k].second;
}
}
}
}
return rc;
}
pCourse oClass::getCourse(int leg, unsigned fork, bool getSampleFromRunner) const {
leg = mapLeg(leg);
if (size_t(leg) < MultiCourse.size()) {
const vector &courses=MultiCourse[leg];
if (courses.size()>0) {
int index = fork;
if (index>0)
index = (index-1) % courses.size();
return courses[index];
}
}
if (!getSampleFromRunner)
return nullptr;
else {
pCourse res = 0;
for (oRunnerList::iterator it = oe->Runners.begin(); it != oe->Runners.end(); ++it) {
if (it->getClassRef(true) == this && it->Course) {
if (it->tLeg == leg)
return it->Course;
else
res = it->Course; // Might find better candidate later
}
}
return res;
}
}
pCourse oClass::getCourse(bool getSampleFromRunner) const {
pCourse res;
if (MultiCourse.size() == 1 && MultiCourse[0].size() == 1)
res = MultiCourse[0][0];
else
res = Course;
if (!res && getSampleFromRunner)
res = getCourse(0,0, true);
return res;
}
void oClass::getCourses(int leg, vector &courses) const {
leg = mapLeg(leg);
//leg == -1 -> all courses
courses.clear();
set added;
if (leg <= 0 && Course)
courses.push_back(Course);
for (size_t cl = 0; cl < MultiCourse.size(); cl++) {
if (leg>= 0 && cl != leg)
continue;
const vector &mc = MultiCourse[cl];
for (size_t k = 0; k < mc.size(); k++)
if (added.insert(mc[k]->Id).second)
courses.push_back(mc[k]);
}
// Add shortened versions
for (size_t k = 0; k < courses.size(); k++) {
pCourse sht = courses[k]->getShorterVersion().second;
int maxIter = 10;
while (sht && --maxIter >= 0 ) {
if (added.insert(sht->Id).second)
courses.push_back(sht);
sht = sht->getShorterVersion().second;
}
}
}
ClassType oClass::getClassType() const
{
if (legInfo.size()==2 && (legInfo[1].isParallel() ||
legInfo[1].legMethod==LTIgnore) )
return oClassPatrol;
else if (legInfo.size()>=2) {
if (isQualificationFinalBaseClass())
return oClassKnockout;
for(size_t k=1;k=nleg)
return 1;
int nP = 1;
int i = leg;
while (++i=0 && legInfo[i--].isParallel())
nP++;
return nP;
}
int oClass::getNumDistinctRunners() const
{
if (legInfo.empty())
return 1;
int ndist=0;
for (size_t k=0;k=MultiCourse.size())
return Course;
// First = course to check, second = course to assign. First could be a shortened version.
vector > layer(MultiCourse[leg].size());
for (size_t k = 0; k < layer.size(); k++) {
layer[k].first = MultiCourse[leg][k];
layer[k].second = MultiCourse[leg][k];
}
while (Distance < 0 && !layer.empty()) {
for (size_t k=0;k < layer.size(); k++) {
if (layer[k].first) {
int d = layer[k].first->distance(card);
if (d>=0) {
if (Distance<0) Distance=1000;
if (dDistance) {
Distance=d;
rc = layer[k].second;
}
}
}
}
if (Distance < 0) {
// If we have found no acceptable match, try the shortened courses, if any
vector< pair > shortenedLayer;
for (size_t k=0;k < layer.size(); k++) {
if (layer[k].first) {
pCourse sw = layer[k].first->getShorterVersion().second;
if (sw)
shortenedLayer.push_back(make_pair(sw, layer[k].second));
}
}
swap(layer, shortenedLayer);
}
}
return const_cast(rc);
}
void oClass::updateChangedCoursePool() {
if (!tCoursesChanged)
return;
bool hasPool = hasCoursePool();
vector< set > crs;
for (size_t k = 0; k < MultiCourse.size(); k++) {
crs.push_back(set());
for (size_t j = 0; j < MultiCourse[k].size(); j++) {
if (MultiCourse[k][j])
crs.back().insert(MultiCourse[k][j]);
}
}
SICard card(ConvertedTimeStatus::Unknown);
oRunnerList::iterator it;
for (it = oe->Runners.begin(); it != oe->Runners.end(); ++it) {
if (it->isRemoved() || it->getClassRef(true) != this)
continue;
if (size_t(it->tLeg) >= crs.size() || crs[it->tLeg].empty())
continue;
if (!hasPool) {
if (it->Course) {
it->setCourseId(0);
it->synchronize();
}
}
else {
bool correctCourse = crs[it->tLeg].count(it->Course) > 0;
if ((!correctCourse || (correctCourse && it->tStatus == StatusMP)) && it->Card) {
it->Card->getSICard(card);
pCourse crs = selectCourseFromPool(it->tLeg, card);
if (crs != it->Course) {
it->setCourseId(crs->getId());
it->synchronize();
}
}
}
}
tCoursesChanged = false;
}
oClass::LeaderInfo &oClass::getLeaderInfo(AllowRecompute recompute, int leg) const {
if (recompute == AllowRecompute::Yes && leaderTimeVersion != oe->dataRevision)
updateLeaderTimes();
leg = mapLeg(leg);
if (leg < 0)
throw meosException();
if (recompute == AllowRecompute::NoUseOld && size_t(leg) < tLeaderTimeOld.size())
return tLeaderTimeOld[leg];
if (size_t(leg) >= tLeaderTime.size())
tLeaderTime.resize(leg + 1);
return tLeaderTime[leg];
}
bool oClass::LeaderInfo::updateComputed(int rt, Type t) {
bool update = false;
switch (t) {
case Type::Leg:
if (bestTimeOnLegComputed <= 0 || bestTimeOnLegComputed > rt)
bestTimeOnLegComputed = rt, update = true;
break;
case Type::Total:
if (totalLeaderTimeComputed <= 0 || totalLeaderTimeComputed > rt)
totalLeaderTimeComputed = rt, update = true;
break;
case Type::TotalInput:
if (totalLeaderTimeInputComputed <= 0 || totalLeaderTimeInputComputed > rt)
totalLeaderTimeInputComputed = rt, update = true;
break;
default:
assert(false);
}
return update;
}
bool oClass::LeaderInfo::update(int rt, Type t) {
bool update = false;
switch (t) {
case Type::Leg:
if (rt >= 0 && (bestTimeOnLeg < 0 || bestTimeOnLeg > rt))
bestTimeOnLeg = rt, update = true;
break;
case Type::Total:
if (rt >= 0 && (totalLeaderTime < 0 || totalLeaderTime > rt))
totalLeaderTime = rt, update = true;
break;
case Type::TotalInput:
if (rt >= 0 && (totalLeaderTimeInput < 0 || totalLeaderTimeInput > rt))
totalLeaderTimeInput = rt, update = true;
break;
case Type::Input:
if (rt >= 0 && (inputTime < 0 || inputTime > rt))
inputTime = rt, update = true;
break;
default:
assert(false);
}
return update;
}
void oClass::updateLeaderTimes() const {
resetLeaderTime();
vector runners;
oe->getRunners(Id, 0, runners, false);
bool needupdate = true;
int leg = 0;
while (needupdate) {
needupdate = false;
for (auto r : runners) {
if (r->tLeg == leg)
r->storeTimes();
else if (r->tLeg > leg)
needupdate = true;
}
if (leg >= tLeaderTime.size())
break;
tLeaderTime[leg].setComplete();
leg++;
}
leaderTimeVersion = oe->dataRevision;
}
void oClass::LeaderInfo::resetComputed(Type t) {
switch (t) {
case Type::Leg:
bestTimeOnLegComputed = 0;
break;
case Type::Total:
totalLeaderTimeComputed = 0;
break;
case Type::TotalInput:
totalLeaderTimeInputComputed = 0;
break;
}
}
int oClass::LeaderInfo::getLeader(Type t, bool computed) const {
switch (t) {
case Type::Leg:
if (computed && bestTimeOnLegComputed > 0)
return bestTimeOnLegComputed;
else
return bestTimeOnLeg;
case Type::Total:
if (computed && totalLeaderTimeComputed > 0)
return totalLeaderTimeComputed;
else
return totalLeaderTime;
case Type::TotalInput:
if (computed && totalLeaderTimeInputComputed > 0)
return totalLeaderTimeInputComputed;
else if (totalLeaderTimeInput > 0)
return totalLeaderTimeInput;
else
return inputTime;
}
return 0;
}
int oClass::getBestLegTime(AllowRecompute recompute, int leg, bool computedTime) const {
leg = mapLeg(leg);
if (unsigned(leg) >= tLeaderTime.size())
return 0;
int bt = getLeaderInfo(recompute, leg).getLeader(LeaderInfo::Type::Leg, computedTime);
if (bt == -1 && recompute == AllowRecompute::Yes) {
updateLeaderTimes();
bt = tLeaderTime[leg].getInputTime();
}
return bt;
}
int oClass::getBestTimeCourse(AllowRecompute recompute, int courseId) const {
if (recompute == AllowRecompute::Yes && leaderTimeVersion != oe->dataRevision)
updateLeaderTimes();
map::const_iterator res = tBestTimePerCourse.find(courseId);
if (res == tBestTimePerCourse.end())
return 0;
else
return res->second;
}
int oClass::getBestInputTime(AllowRecompute recompute, int leg) const {
leg = mapLeg(leg);
if (unsigned(leg)>=tLeaderTime.size())
return 0;
else {
int it = getLeaderInfo(recompute, leg).getInputTime();
if (it == -1 && recompute == AllowRecompute::Yes) {
updateLeaderTimes();
it = getLeaderInfo(AllowRecompute::No, leg).getInputTime();
}
return -1;
}
}
int oClass::getTotalLegLeaderTime(AllowRecompute recompute, int leg, bool computedTime, bool includeInput) const {
leg = mapLeg(leg);
if (unsigned(leg) >= tLeaderTime.size())
return 0;
int res = -1;
int iter = -1;
bool mayUseOld = recompute == AllowRecompute::NoUseOld;
if (mayUseOld)
recompute = AllowRecompute::No;
while (res == -1 && ++iter<2) {
if (includeInput)
res = getLeaderInfo(recompute, leg).getLeader(LeaderInfo::Type::TotalInput, computedTime);
else
res = getLeaderInfo(recompute, leg).getLeader(LeaderInfo::Type::Total, computedTime);
if (res == -1 && recompute == AllowRecompute::Yes) {
recompute = AllowRecompute::No;
updateLeaderTimes();
}
else if (res == -1 && mayUseOld)
recompute = AllowRecompute::NoUseOld;
}
return res;
}
void oClass::mergeClass(int classIdSec) {
vector t;
vector r;
vector rThis;
oe->getRunners(classIdSec, 0, rThis, true);
// Update teams
oe->getTeams(classIdSec, t, true);
oe->getRunners(classIdSec, 0, r, false);
for (size_t k = 0; k < t.size(); k++) {
pTeam it = t[k];
it->Class = this;
it->updateChanged();
for (size_t k=0;kRunners.size();k++) {
if (it->Runners[k]) {
it->Runners[k]->Class = this;
it->Runners[k]->updateChanged();
}
}
it->synchronize(); //Synchronizes runners also
}
// Update runners
for (size_t k = 0; k < r.size(); k++) {
pRunner it = r[k];
it->Class = this;
it->updateChanged();
it->synchronize();
}
oe->classIdToRunnerHash.reset();
// Check heats
int maxHeatThis = 0;
bool missingHeatThis = false, uniqueHeatThis = true;
for (size_t k = 0; k < rThis.size(); k++) {
int heat = rThis[k]->getDCI().getInt("Heat");
if (heat == 0)
missingHeatThis = true;
if (maxHeatThis != 0 && heat != maxHeatThis)
uniqueHeatThis = false;
maxHeatThis = max(maxHeatThis, heat);
}
int maxHeatOther = 0;
bool missingHeatOther = false, uniqueHeatOther = true;
for (size_t k = 0; k < r.size(); k++) {
int heat = r[k]->getDCI().getInt("Heat");
if (heat == 0)
missingHeatOther = true;
if (maxHeatOther != 0 && heat != maxHeatOther)
uniqueHeatOther = false;
maxHeatOther = max(maxHeatOther, heat);
}
int heatForNext = 1;
if (missingHeatThis) {
for (size_t k = 0; k < rThis.size(); k++) {
int heat = rThis[k]->getDCI().getInt("Heat");
if (heat == 0) {
if (uniqueHeatThis && maxHeatThis > 0)
heat = maxHeatThis; // Some runners are missing the heat info. Fill in.
else {
// If maxHeatthis> 0, data somehow corrupted:
// Some runners have heat, but not unqiue,
// others are missing. Heats not well defined.
heat = maxHeatThis + 1;
}
}
heatForNext = max(heatForNext, heat+1);
rThis[k]->getDI().setInt("Heat", heat);
}
}
if (missingHeatOther) {
for (size_t k = 0; k < r.size(); k++) {
int heat = r[k]->getDCI().getInt("Heat");
if (heat == 0) {
if (maxHeatOther == 0)
heat = heatForNext; // No runner had a heat, set to next heat
else if (uniqueHeatOther)
heat = maxHeatOther; // Some runner missing the heat. Use the defined heat.
else
heat = maxHeatOther + 1; // Data corrupted, see above. Make a unique heat.
}
r[k]->getDI().setInt("Heat", heat);
}
}
// Write back
for (size_t k = 0; k < t.size(); k++) {
t[k]->synchronize(true); //Synchronizes runners also
}
for (size_t k = 0; k < r.size(); k++) {
r[k]->synchronize(true);
}
for (size_t k = 0; k < rThis.size(); k++) {
rThis[k]->synchronize(true);
}
oe->removeClass(classIdSec);
}
void oClass::getSplitMethods(vector< pair > &methods) {
methods.clear();
methods.push_back(make_pair(lang.tl("Dela klubbvis"), SplitClub));
methods.push_back(make_pair(lang.tl("Dela slumpmässigt"), SplitRandom));
methods.push_back(make_pair(lang.tl("Dela efter ranking"), SplitRank));
methods.push_back(make_pair(lang.tl("Dela efter placering"), SplitResult));
methods.push_back(make_pair(lang.tl("Dela efter tid"), SplitTime));
methods.push_back(make_pair(lang.tl("Jämna klasser (ranking)"), SplitRankEven));
methods.push_back(make_pair(lang.tl("Jämna klasser (placering)"), SplitResultEven));
methods.push_back(make_pair(lang.tl("Jämna klasser (tid)"), SplitTimeEven));
}
class ClassSplit {
private:
map clubSize;
map idSplit;
map clubSplit;
vector runners;
void splitClubs(const vector &parts);
void valueSplit(const vector &parts, vector< pair > &valueId);
void valueEvenSplit(const vector &parts, vector< pair > &valueId);
public:
static int evaluateTime(const oAbstractRunner &r) {
if (r.getInputStatus() == StatusOK) {
int t = r.getInputTime();
if (t > 0)
return t;
else
return timeConstHour * 24 * 8;
}
else {
return timeConstHour * 24 * 8 + r.getId();
}
}
static int evaluateResult(const oAbstractRunner &r) {
int baseRes;
if (r.getInputStatus() == StatusOK) {
int t = r.getInputPlace();
if (t == 0) {
const oRunner *rr = dynamic_cast(&r);
if (rr && rr->getTeam() && rr->getLegNumber() > 0) {
const pRunner rPrev = rr->getTeam()->getRunner(rr->getLegNumber() - 1);
if (rPrev && rPrev->getStatus() == StatusOK)
t = rPrev->getPlace();
}
}
if (t > 0)
baseRes = t;
else
baseRes = 99999;
}
else {
baseRes = 99999 + r.getInputStatus();
}
return r.getDCI().getInt("Heat") + 1000 * baseRes;
}
static int evaluatePoints(const oAbstractRunner &r) {
if (r.getInputStatus() == StatusOK) {
int p = r.getInputPoints();
if (p > 0)
return 1000*1000*1000 - p;
else
return 1000*1000*1000;
}
else {
return 1000*1000*1000 + r.getInputStatus();
}
}
private:
int evaluate(const oAbstractRunner &r, ClassSplitMethod method) {
switch (method) {
case SplitRank:
case SplitRankEven:
return r.getRanking();
case SplitTime:
case SplitTimeEven:
return evaluateTime(r);
case SplitResult:
case SplitResultEven:
return evaluateResult(r);
default:
throw meosException("Not yet implemented");
}
}
public:
void addMember(const oAbstractRunner &r) {
++clubSize[r.getClubId()];
runners.push_back(&r);
}
void split(const vector &parts, ClassSplitMethod method);
int getClassIndex(const oAbstractRunner &r) {
if (clubSplit.count(r.getClubId()))
return clubSplit[r.getClubId()];
else if (idSplit.count(r.getId()))
return idSplit[r.getId()];
throw meosException("Internal split error");
}
};
void ClassSplit::split(const vector &parts, ClassSplitMethod method) {
switch (method) {
case SplitClub:
splitClubs(parts);
break;
case SplitRank:
case SplitTime:
case SplitResult: {
vector< pair > v(runners.size());
for (size_t k = 0; k < v.size(); k++) {
v[k].second = runners[k]->getId();
v[k].first = evaluate(*runners[k], method);
}
valueSplit(parts, v);
} break;
case SplitRankEven:
case SplitTimeEven:
case SplitResultEven: {
vector< pair > v(runners.size());
for (size_t k = 0; k < v.size(); k++) {
v[k].second = runners[k]->getId();
v[k].first = evaluate(*runners[k], method);
}
valueEvenSplit(parts, v);
} break;
case SplitRandom: {
vector r(runners.size());
for (size_t k = 0; k < r.size(); k++) {
r[k] = k;
}
permute(r);
vector< pair > v(runners.size());
for (size_t k = 0; k < v.size(); k++) {
v[k].second = runners[k]->getId();
v[k].first = r[k];
}
valueEvenSplit(parts, v);
break;
}
default:
throw meosException("Not yet implemented");
}
}
void ClassSplit::splitClubs(const vector &parts) {
vector classSize(parts);
while ( !clubSize.empty() ) {
// Find largest club
int club=0;
int size=0;
for (map::iterator it=clubSize.begin(); it!=clubSize.end(); ++it) {
if (it->second>size) {
club = it->first;
size = it->second;
}
}
clubSize.erase(club);
// Find smallest class (e.g. highest number of remaining)
int nrunner = -1000000;
int cid = 0;
for(size_t k = 0; k < parts.size(); k++) {
if (classSize[k]>nrunner) {
nrunner = classSize[k];
cid = k;
}
}
//Store result
clubSplit[club] = cid;
classSize[cid] -= size;
}
}
void ClassSplit::valueSplit(const vector &parts, vector< pair > &valueId) {
sort(valueId.begin(), valueId.end());
int partIx = 0;
int partCount = 0;
for (size_t k = 0; k < valueId.size(); ) {
int refValue = valueId[k].first;
for (; k < valueId.size() && valueId[k].first == refValue; k++) {
idSplit[valueId[k].second] = partIx;
partCount++;
}
if (k < valueId.size() && partCount >= parts[partIx] && size_t(partIx + 1) < parts.size()) {
partIx++;
partCount = 0;
}
}
if (partIx == 0) {
throw meosException("error:invalidmethod");
}
}
void ClassSplit::valueEvenSplit(const vector &parts, vector< pair > &valueId) {
sort(valueId.begin(), valueId.end());
if (valueId.empty() || valueId.front().first == valueId.back().first) {
throw meosException("error:invalidmethod");
}
vector count(parts.size());
bool odd = true;
bool useRandomAssign = false;
for (size_t k = 0; k < valueId.size(); ) {
vector distr;
for (size_t j = 0; k < valueId.size() && j < parts.size(); j++) {
if (count[j] < parts[j]) {
distr.push_back(valueId[k++].second);
}
}
if (distr.empty()) {
idSplit[valueId[k++].second] = parts.size()-1; // Out of space, use last for rest
}
else {
if (useRandomAssign) {
permute(distr); //Random assignment to groups
}
else {
// Use reverse/forward distribution. Swedish SM rules
if (odd)
reverse(distr.begin(), distr.end());
odd = !odd;
}
for (size_t j = 0; j < parts.size(); j++) {
if (count[j] < parts[j]) {
++count[j];
idSplit[distr.back()] = j;
distr.pop_back();
}
}
}
}
}
void oClass::splitClass(ClassSplitMethod method, const vector &parts, vector &outClassId) {
if (parts.size() <= 1)
return;
bool qf = false;
set clsIdSrc;
clsIdSrc.insert(getId());
if (getQualificationFinal()) {
// Works for base classes
set base;
getQualificationFinal()->getBaseClassInstances(base);
assert(base.size() == parts.size());
qf = true;
for (int inst : base)
clsIdSrc.insert(getVirtualClass(inst)->getId());
}
bool defineHeats = method == SplitRankEven || method == SplitResultEven;
ClassSplit cc;
vector t;
vector r;
if (!qf && oe->classHasTeams(getId()) ) {
for (int clsId : clsIdSrc) {
vector tTmp;
oe->getTeams(clsId, tTmp, true);
for (auto tk : tTmp) {
t.push_back(tk);
cc.addMember(*tk);
}
}
}
else {
for (int clsId : clsIdSrc) {
vector rTmp;
oe->getRunners(clsId, 0, rTmp, true);
for (auto rk : rTmp) {
if (qf && rk->getLegNumber() != 0)
continue;
r.push_back(rk);
cc.addMember(*rk);
}
}
}
// Split teams.
cc.split(parts, method);
vector pcv(parts.size());
outClassId.resize(parts.size());
if (qf) {
set base;
getQualificationFinal()->getBaseClassInstances(base);
int ix = 0;
for (int inst : base) {
pcv[ix] = getVirtualClass(inst);
outClassId[ix] = pcv[ix]->getId();
ix++;
}
}
else {
pcv[0] = this;
outClassId[0] = getId();
pcv[0]->getDI().setInt("Heat", defineHeats ? 1 : 0);
pcv[0]->synchronize(true);
int lastSI = getDI().getInt("SortIndex");
for (size_t k = 1; k < parts.size(); k++) {
pcv[k] = oe->addClass(getName() + makeDash(L"-") + itow(k + 1), getCourseId());
if (pcv[k]) {
// Find suitable sort index
lastSI = pcv[k]->getSortIndex(lastSI + 1);
memcpy(pcv[k]->oData, oData, sizeof(oData));
pcv[k]->getDI().setInt("SortIndex", lastSI);
pcv[k]->getDI().setInt("Heat", defineHeats ? k + 1 : 0);
pcv[k]->synchronize();
}
outClassId[k] = pcv[k]->getId();
}
setName(getName() + makeDash(L"-1"), false);
synchronize();
}
for (size_t k = 0; k < t.size(); k++) {
pTeam it = t[k];
int clsIx = cc.getClassIndex(*it);
it->Class = pcv[clsIx];
it->updateChanged();
for (size_t k=0;kRunners.size();k++) {
if (it->Runners[k]) {
if (defineHeats)
it->getDI().setInt("Heat", clsIx+1);
it->Runners[k]->Class = it->Class;
it->Runners[k]->updateChanged();
}
}
it->synchronize(); //Synchronizes runners also
}
for (size_t k = 0; k < r.size(); k++) {
pRunner it = r[k];
int clsIx = cc.getClassIndex(*it);
if (qf) {
it->getDI().setInt("Heat", clsIx + 1);
}
else {
it->Class = pcv[clsIx];
if (defineHeats)
it->getDI().setInt("Heat", clsIx + 1);
}
it->updateChanged();
it->synchronize();
}
oe->classIdToRunnerHash.reset();
}
void oClass::getAgeLimit(int &low, int &high) const
{
low = getDCI().getInt("LowAge");
high = getDCI().getInt("HighAge");
}
void oClass::setAgeLimit(int low, int high)
{
getDI().setInt("LowAge", low);
getDI().setInt("HighAge", high);
}
int oClass::getExpectedAge() const
{
int low, high;
getAgeLimit(low, high);
if (low>0 && high>0)
return (low+high)/2;
if (low==0 && high>0)
return high-3;
if (low>0 && high==0)
return low + 1;
// Try to guess age from class name
for (size_t k=0; k='0' && Name[k]<='9') {
int age = _wtoi(&Name[k]);
if (age>=10 && age<100) {
if (age>=10 && age<=20)
return age - 1;
else if (age==21)
return 28;
else if (age>=35)
return age + 2;
}
}
}
return 0;
}
void oClass::setSex(PersonSex sex)
{
getDI().setString("Sex", encodeSex(sex));
}
PersonSex oClass::getSex() const
{
return interpretSex(getDCI().getString("Sex"));
}
void oClass::setStart(const wstring &start)
{
getDI().setString("StartName", start);
}
wstring oClass::getStart() const
{
return getDCI().getString("StartName");
}
void oClass::setBlock(int block)
{
getDI().setInt("StartBlock", block);
}
int oClass::getBlock() const
{
return getDCI().getInt("StartBlock");
}
void oClass::setAllowQuickEntry(bool quick)
{
getDI().setInt("AllowQuickEntry", quick);
}
bool oClass::getAllowQuickEntry() const
{
return getDCI().getInt("AllowQuickEntry")!=0;
}
void oClass::setNoTiming(bool quick)
{
tNoTiming = quick ? 1 : 0;
getDI().setInt("NoTiming", quick);
}
BibMode oClass::getBibMode() const {
const wstring &bm = getDCI().getString("BibMode");
wchar_t b = bm.c_str()[0];
if (b == 'A')
return BibAdd;
else if (b == 'F')
return BibFree;
else if (b == 'L')
return BibLeg;
else
return BibSame;
}
void oClass::setBibMode(BibMode bibMode) {
wstring res;
switch (bibMode) {
case BibAdd:
res = L"A";
break;
case BibFree:
res = L"F";
break;
case BibLeg:
res = L"L";
break;
case BibSame:
res = L"";
break;
default:
throw meosException("Invalid bib mode");
}
getDI().setString("BibMode", res);
}
bool oClass::getNoTiming() const {
if (tNoTiming!=0 && tNoTiming!=1)
tNoTiming = getDCI().getInt("NoTiming")!=0 ? 1 : 0;
return tNoTiming!=0;
}
void oClass::setIgnoreStartPunch(bool ignoreStartPunch) {
tIgnoreStartPunch = ignoreStartPunch;
getDI().setInt("IgnoreStart", ignoreStartPunch);
}
bool oClass::ignoreStartPunch() const {
if (tIgnoreStartPunch!=0 && tIgnoreStartPunch!=1)
tIgnoreStartPunch = getDCI().getInt("IgnoreStart")!=0 ? 1 : 0;
return tIgnoreStartPunch != 0;
}
void oClass::setFreeStart(bool quick)
{
getDI().setInt("FreeStart", quick);
}
bool oClass::hasFreeStart() const
{
bool fs = getDCI().getInt("FreeStart") != 0;
return fs;
}
void oClass::setDirectResult(bool quick)
{
getDI().setInt("DirectResult", quick);
}
bool oClass::hasDirectResult() const
{
return getDCI().getInt("DirectResult") != 0;
}
void oClass::setType(const wstring &start)
{
getDI().setString("ClassType", start);
}
wstring oClass::getType() const
{
return getDCI().getString("ClassType");
}
void oEvent::fillStarts(gdioutput &gdi, const string &id)
{
vector< pair > d;
oe->fillStarts(d);
gdi.addItem(id, d);
}
const vector< pair > &oEvent::fillStarts(vector< pair > &out)
{
out.clear();
set starts;
for (oClassList::iterator it = Classes.begin(); it!=Classes.end(); ++it) {
if (!it->getStart().empty())
starts.insert(it->getStart());
}
if (starts.empty())
starts.insert(lang.tl(L"Start") + L" 1");
for (set::iterator it = starts.begin(); it!=starts.end(); ++it) {
//gdi.addItem(id, *it);
out.push_back(make_pair(*it, 0));
}
return out;
}
void oEvent::fillClassTypes(gdioutput &gdi, const string &id)
{
vector< pair > d;
oe->fillClassTypes(d);
gdi.addItem(id, d);
}
ClassMetaType oClass::interpretClassType() const {
int lowAge;
int highAge;
getAgeLimit(lowAge, highAge);
if (highAge>0 && highAge <= 16)
return ctYouth;
map types;
oe->getPredefinedClassTypes(types);
wstring type = getType();
for (map::iterator it = types.begin(); it != types.end(); ++it) {
if (type == it->first || type == lang.tl(it->first))
return it->second;
}
if (oe->classTypeNameToType.empty()) {
// Lazy readout of baseclasstypes
wchar_t path[_MAX_PATH];
getUserFile(path, L"baseclass.xml");
xmlparser xml;
xml.read(path);
xmlobject cType = xml.getObject("BaseClassTypes");
xmlList xtypes;
cType.getObjects("Type", xtypes);
for (size_t k = 0; kclassTypeNameToType[name] = mtype;
}
}
if (oe->classTypeNameToType.count(type) == 1)
return oe->classTypeNameToType[type];
return ctUnknown;
}
void oClass::assignTypeFromName(){
wstring type = getType();
if (type.empty()) {
wstring prefix, suffix;
extractAnyNumber(Name, prefix, suffix);
int age = getExpectedAge();
ClassMetaType mt = ctUnknown;
if (age>=18) {
if (stringMatch(suffix, lang.tl(L"Elit")) || wcschr(suffix.c_str(), 'E'))
mt = ctElite;
else if (stringMatch(suffix, lang.tl(L"Motion")) || wcschr(suffix.c_str(), 'M'))
mt = ctExercise;
else
mt = ctNormal;
}
else if (age>=10 && age<=16) {
mt = ctYouth;
}
else if (age<10) {
if (stringMatch(prefix, lang.tl(L"Ungdom")) || wcschr(prefix.c_str(), 'U')
|| stringMatch(prefix, L"insk") || stringMatch(prefix, lang.tl(L"Inskolning")))
mt = ctYouth;
else if (stringMatch(suffix, lang.tl(L"Motion")) || wcschr(suffix.c_str(), 'M'))
mt = ctExercise;
else
mt = ctOpen;
}
map types;
oe->getPredefinedClassTypes(types);
for (map::iterator it = types.begin(); it != types.end(); ++it) {
if (it->second == mt) {
setType(lang.tl(it->first));
return;
}
}
}
}
void oEvent::getPredefinedClassTypes(map &types) const {
types.clear();
types[L"Elit"] = ctElite;
types[L"Vuxen"] = ctNormal;
types[L"Ungdom"] = ctYouth;
types[L"Motion"] = ctExercise;
types[L"Öppen"] = ctOpen;
types[L"Träning"] = ctTraining;
}
const vector< pair > &oEvent::fillClassTypes(vector< pair > &out)
{
out.clear();
set cls;
bool allHasType = !Classes.empty();
for (oClassList::iterator it = Classes.begin(); it!=Classes.end(); ++it) {
if (it->isRemoved())
continue;
if (!it->getType().empty())
cls.insert(it->getType());
else
allHasType = false;
}
if (!allHasType) {
map types;
getPredefinedClassTypes(types);
for (map::iterator it = types.begin(); it != types.end(); ++it)
cls.insert(lang.tl(it->first));
}
for (set::iterator it = cls.begin(); it!=cls.end(); ++it) {
//gdi.addItem(id, *it);
out.push_back(make_pair(*it, 0));
}
return out;
}
int oClass::getNumRemainingMaps(bool forceRecalculate) const {
oe->calculateNumRemainingMaps(forceRecalculate);
int numMaps = tMapsRemaining;
if (Course && Course->tMapsRemaining != numeric_limits::min()) {
if (numMaps == numeric_limits::min())
numMaps = Course->tMapsRemaining;
else
numMaps = min(numMaps, Course->tMapsRemaining);
}
return numMaps;
}
void oClass::setNumberMaps(int nm) {
getDI().setInt("NumberMaps", nm);
}
int oClass::getNumberMaps(bool rawAttribute) const {
int nm = getDCI().getInt("NumberMaps");
if (rawAttribute)
return nm;
if (nm == 0 && Course)
nm = Course->getNumberMaps();
return nm;
}
void oEvent::getStartBlocks(vector &blocks, vector &starts) const
{
oClassList::const_iterator it;
set> bs;
for (it = Classes.begin(); it != Classes.end(); ++it) {
if (it->isRemoved())
continue;
bs.emplace(it->getStart(), it->getBlock());
}
blocks.clear();
starts.clear();
for (auto &v : bs) {
blocks.push_back(v.second);
starts.push_back(v.first);
}
}
const shared_ptr &oClass::getTable(oEvent *oe) {
if (!oe->hasTable("class")) {
auto table = make_shared(oe, 20, L"Klasser", "classes");
table->addColumn("Id", 70, true, true);
table->addColumn("Ändrad", 70, false);
table->addColumn("Namn", 200, false);
table->addColumn("Bana", 200, false);
table->addColumn("Anmälda", 70, true);
oe->oClassData->buildTableCol(table.get());
oe->setTable("class", table);
}
return oe->getTable("class");
}
void oEvent::generateClassTableData(Table &table, oClass *addClass)
{
if (addClass) {
addClass->addTableRow(table);
return;
}
synchronizeList(oListId::oLClassId);
oClassList::iterator it;
for (it=Classes.begin(); it != Classes.end(); ++it){
if (!it->isRemoved())
it->addTableRow(table);
}
}
void oClass::addTableRow(Table &table) const {
pClass it = pClass(this);
table.addRow(getId(), it);
int row = 0;
table.set(row++, *it, TID_ID, itow(getId()), false);
table.set(row++, *it, TID_MODIFIED, getTimeStamp(), false);
table.set(row++, *it, TID_CLASSNAME, getName(), true);
wstring crsStr;
pCourse crs = getCourse(false);
bool canEdit = false;
if (crs) {
crsStr = crs->getName();
canEdit = true;
}
else {
if (getNumStages() <= 1 && MultiCourse.empty()) {
crsStr = lang.tl(L"Ingen bana");
canEdit = true;
}
else {
for (size_t leg = 0; leg < MultiCourse.size(); leg++) {
for (size_t j = 0; j < MultiCourse[leg].size(); j++) {
if (j >= 3) {
crsStr += L"…";
break;
}
if (MultiCourse[leg][j]) {
if (!crsStr.empty())
crsStr += L", ";
crsStr += MultiCourse[leg][j]->getName();
}
}
if (!crsStr.empty())
break;
}
}
}
table.set(row++, *it, TID_COURSE, crsStr, canEdit, canEdit ? CellType::cellSelection : CellType::cellEdit);
int numR = getNumRunners(true, false, false);
table.set(row++, *it, TID_NUM, itow(numR), false);
oe->oClassData->fillTableCol(*this, table, true);
}
pair oClass::inputData(int id, const wstring &input,
int inputId, wstring &output, bool noUpdate)
{
synchronize(false);
if (id>1000) {
return oe->oClassData->inputData(this, id, input,
inputId, output, noUpdate);
}
switch(id) {
case TID_CLASSNAME:
setName(input, true);
synchronize();
output=getName();
break;
case TID_COURSE: {
pCourse c = nullptr;
if (inputId != 0)
c = oe->getCourse(inputId);
setCourse(c);
synchronize();
output = input;
}
}
return make_pair(0, false);
}
void oClass::fillInput(int id, vector< pair > &out, size_t &selected)
{
if (id>1000) {
oe->oClassData->fillInput(this, id, 0, out, selected);
return;
}
if (id==TID_COURSE) {
out.clear();
oe->getCourses(out, L"", true);
out.push_back(make_pair(lang.tl(L"Ingen bana"), 0));
pCourse c = getCourse(false);
selected = c ? c->getId() : 0;
}
}
void oClass::getStatistics(const set &feeLock, int &entries, int &started) const
{
oRunnerList::const_iterator it;
entries = 0;
started = 0;
for (it = oe->Runners.begin(); it != oe->Runners.end(); ++it) {
if (it->skip() || it->isVacant())
continue;
if (it->getStatus() == StatusNotCompetiting)
continue;
if (it->getClassId(false)==Id) {
if (feeLock.empty() || feeLock.count(it->getDCI().getInt("Fee"))) {
entries++;
if (it->getStatus()!= StatusUnknown && it->getStatus()!= StatusDNS && it->tStatus != StatusCANCEL)
started++;
}
}
}
}
bool oClass::isSingleRunnerMultiStage() const
{
return getNumStages()>1 && getNumDistinctRunnersMinimal()==1;
}
int oClass::getEntryFee(const wstring &date, int age) const
{
oDataConstInterface odc = oe->getDCI();
wstring oentry = odc.getDate("OrdinaryEntry");
bool late = date > oentry && oentry>=L"2010-01-01";
bool reduced = false;
if (age > 0) {
int low = odc.getInt("YouthAge");
int high = odc.getInt("SeniorAge");
reduced = age <= low || (high > 0 && age >= high);
}
if (reduced) {
int high = getDCI().getInt("HighClassFeeRed");
int normal = getDCI().getInt("ClassFeeRed");
// Only return these fees if set
if (high>0 && late)
return high;
else if (normal>0)
return normal;
}
if (late)
return getDCI().getInt("HighClassFee");
else
return getDCI().getInt("ClassFee");
}
void oClass::addClassDefaultFee(bool resetFee) {
int fee = getDCI().getInt("ClassFee");
if (fee == 0 || resetFee) {
assignTypeFromName();
ClassMetaType type = interpretClassType();
// if (type.empty())
switch (type) {
case ctElite:
fee = oe->getDCI().getInt("EliteFee");
break;
case ctYouth:
fee = oe->getDCI().getInt("YouthFee");
break;
default:
fee = oe->getDCI().getInt("EntryFee");
}
int reducedFee = oe->getDCI().getInt("YouthFee");
double factor = 1.0 + 0.01 * _wtof(oe->getDCI().getString("LateEntryFactor").c_str());
int lateFee = fee;
int lateReducedFee = reducedFee;
if (factor > 1) {
lateFee = int(fee*factor + 0.5);
lateReducedFee = int(reducedFee*factor + 0.5);
}
getDI().setInt("ClassFee", fee);
getDI().setInt("HighClassFee", lateFee);
getDI().setInt("ClassFeeRed", reducedFee);
getDI().setInt("HighClassFeeRed", lateReducedFee);
}
}
void oClass::reinitialize(bool force) const {
if (!force && isInitialized)
return;
isInitialized = true; // Prevent recursion
int ix = getDCI().getInt("SortIndex");
if (ix == 0) {
ix = getSortIndex(getId()*10);
const_cast(this)->getDI().setInt("SortIndex", ix);
}
tSortIndex = ix;
tMaxTime = getDCI().getInt("MaxTime");
if (tMaxTime == 0 && oe) {
tMaxTime = oe->getMaximalTime();
}
wstring wInfo = getDCI().getString("Qualification");
if (!wInfo.empty()) {
if (qualificatonFinal && !qualificatonFinal->matchSerialization(wInfo))
clearQualificationFinal();
if (!qualificatonFinal)
qualificatonFinal = make_shared(MaxClassId, Id);
qualificatonFinal->init(wInfo);
virtualClasses.resize(getNumQualificationFinalClasses());
int nc = qualificatonFinal->getNumClasses();
for (int i = 1; i <= nc; i++)
getVirtualClass(i);
}
else {
clearQualificationFinal();
}
tNoTiming = -1;
tIgnoreStartPunch = -1;
}
void oClass::clearQualificationFinal() const {
if (!qualificatonFinal)
return;
int nc = qualificatonFinal->getNumClasses();
for (pClass pc : virtualClasses) {
if (pc)
pc->parentClass = nullptr;
}
virtualClasses.clear();
qualificatonFinal.reset();
}
void oEvent::reinitializeClasses() const {
for (auto &c : Classes)
c.reinitialize(true);
}
int oClass::getSortIndex(int candidate) const {
int major = numeric_limits::max();
int minor = 0;
for (oClassList::iterator it = oe->Classes.begin(); it != oe->Classes.end(); ++it) {
int ix = it->getDCI().getInt("SortIndex");
if (ix>0) {
if (ix>candidate && ixminor)
minor = ix;
}
}
// If the gap is less than 10 (which is the default), optimize
if (major < numeric_limits::max() && minor>0 && ((major-candidate)<10 || (candidate-minor)<10))
return (major+minor)/2;
else
return candidate;
}
void oClass::apply() {
int trueLeg = 0;
int trueSubLeg = 0;
for (size_t k = 0; kclassChanged(this, false);
}
void oClass::insertLegPlace(int from, int to, int time, int place)
{
if (tLegTimeToPlace) {
int key = time + (to + from*256)*8013;
tLegTimeToPlace->insert(key, place);
}
}
int oClass::getLegPlace(int ifrom, int ito, int time) const
{
if (tLegTimeToPlace) {
int key = time + (ito + ifrom*256)*8013;
int place;
if (tLegTimeToPlace->lookup(key, place))
return place;
}
return 0;
}
void oClass::insertAccLegPlace(int courseId, int controlNo, int time, int place)
{ /*
char bf[256];
sprintf_s(bf, "Insert to %d, %d, time %d\n", courseId, controlNo, time);
OutputDebugString(bf);
*/
if (tLegAccTimeToPlace) {
int key = time + (controlNo + courseId*128)*16013;
tLegAccTimeToPlace->insert(key, place);
}
}
void oClass::getStartRange(int leg, int &firstStart, int &lastStart) const {
leg = mapLeg(leg);
if (tFirstStart.empty()) {
size_t s = getLastStageIndex() + 1;
assert(s>0);
vector lFirstStart, lLastStart;
lFirstStart.resize(s, timeConstHour * 24 * 365);
lLastStart.resize(s, 0);
for (oRunnerList::iterator it = oe->Runners.begin(); it != oe->Runners.end(); ++it) {
if (it->isRemoved() || it->getClassRef(true) != this)
continue;
if (it->needNoCard())
continue;
size_t tleg = mapLeg(it->tLeg);
if (tleg < s) {
lFirstStart[tleg] = min(lFirstStart[tleg], it->tStartTime);
lLastStart[tleg] = max(lLastStart[tleg], it->tStartTime);
}
}
swap(tLastStart, lLastStart);
swap(tFirstStart, lFirstStart);
}
if (size_t(leg) < tFirstStart.size()) {
firstStart = tFirstStart[leg];
lastStart = tLastStart[leg];
}
else if (!tFirstStart.empty()) {
firstStart = tFirstStart[0];
lastStart = tLastStart[0];
for (size_t k = 1; k < tFirstStart.size(); k++) {
if (lastStart > 0) {
firstStart = min(tFirstStart[k], firstStart);
lastStart = max(tLastStart[k], lastStart);
}
}
}
else {
firstStart = 0;
lastStart = 0;
}
}
int oClass::getAccLegPlace(int courseId, int controlNo, int time) const
{/*
char bf[256];
sprintf_s(bf, "Get from %d, %d, time %d\n", courseId, controlNo, time);
OutputDebugString(bf);
*/
if (tLegAccTimeToPlace) {
int key = time + (controlNo + courseId*128)*16013;
int place;
if (tLegAccTimeToPlace->lookup(key, place))
return place;
}
return 0;
}
void oClass::calculateSplits() {
clearSplitAnalysis();
set cSet;
map > legToTime;
for (size_t k=0;k rCls;
oe->getRunners(Id, -1, rCls, false);
/*
if (isQualificationFinalBaseClass() || isQualificationFinalBaseClass()) {
for (auto &r : oe->Runners) {
if (!r.isRemoved() && r.getClassRef(true) == this)
rCls.push_back(&r);
}
}
else {
for (auto &r : oe->Runners) {
if (!r.isRemoved() && r.Class == this)
rCls.push_back(&r);
}
}*/
for (set::iterator cit = cSet.begin(); cit!= cSet.end(); ++cit) {
pCourse pc = *cit;
// Store all split times in a matrix
const unsigned nc = pc->getNumControls();
if (nc == 0)
return;
vector< vector > splits(nc+1);
vector< vector > splitsAcc(nc+1);
vector acceptMissingPunch(nc+1, true);
for (pRunner it : rCls) {
pCourse tpc = it->getCourse(false);
if (tpc != pc || tpc == 0)
continue;
const vector &sp = it->getSplitTimes(true);
const int s = min(nc, sp.size());
for (int k = 0; k < s; k++) {
if (sp[k].getTime(true) > 0 && acceptMissingPunch[k]) {
pControl ctrl = tpc->getControl(k);
// If there is a
if (ctrl && ctrl->getStatus() != oControl::ControlStatus::StatusBad && ctrl->getStatus() != oControl::ControlStatus::StatusOptional)
acceptMissingPunch[k] = false;
}
}
}
for (pRunner it : rCls) {
pCourse tpc = it->getCourse(false);
if (tpc != pc)
continue;
const vector &sp = it->getSplitTimes(true);
const int s = min(nc, sp.size());
vector &tLegTimes = it->tLegTimes;
tLegTimes.resize(nc + 1);
bool ok = true;
for (int k = 0; k < s; k++) {
if (sp[k].getTime(true) > 0) {
if (ok) {
// Store accumulated times
int t = sp[k].getTime(true) - it->tStartTime;
if (it->tStartTime>0 && t>0)
splitsAcc[k].push_back(t);
}
if (k == 0) { // start -> first
int t = sp[0].getTime(true) - it->tStartTime;
if (it->tStartTime>0 && t>0) {
splits[k].push_back(t);
tLegTimes[k] = t;
}
else
tLegTimes[k] = 0;
}
else { // control -> control
int t = sp[k].getTime(true) - sp[k-1].getTime(true);
if (sp[k-1].getTime(true)>0 && t>0) {
splits[k].push_back(t);
tLegTimes[k] = t;
}
else
tLegTimes[k] = 0;
}
}
else
ok = acceptMissingPunch[k];
}
// last -> finish
if (sp.size() == nc && sp[nc-1].getTime(true)>0 && it->FinishTime > 0) {
int t = it->FinishTime - sp[nc-1].getTime(true);
if (t>0) {
splits[nc].push_back(t);
tLegTimes[nc] = t;
if (it->statusOK(true, false) && (it->FinishTime - it->tStartTime) > 0) {
splitsAcc[nc].push_back(it->FinishTime - it->tStartTime);
}
}
else
tLegTimes[nc] = 0;
}
}
if (splits.size()>0 && tLegTimeToPlace == 0) {
tLegTimeToPlace = new inthashmap(splits.size() * splits[0].size());
tLegAccTimeToPlace = new inthashmap(splits.size() * splits[0].size());
}
vector &accLeaderTime = tCourseAccLegLeaderTime[pc->getId()];
for (size_t k = 0; k < splits.size(); k++) {
// Calculate accumulated best times and places
if (!splitsAcc[k].empty()) {
sort(splitsAcc[k].begin(), splitsAcc[k].end());
accLeaderTime.push_back(splitsAcc[k].front()); // Store best time
int place = 1;
for (size_t j = 0; j < splitsAcc[k].size(); j++) {
if (j>0 && splitsAcc[k][j-1]getId(), k, splitsAcc[k][j], place);
}
}
else {
// Bad control / missing times
int t = 0;
if (!accLeaderTime.empty())
t = accLeaderTime.back();
accLeaderTime.push_back(t); // Store time from previous leg
}
sort(splits[k].begin(), splits[k].end());
const size_t ntimes = splits[k].size();
if (ntimes == 0)
continue;
int from = pc->getCommonControl(), to = pc->getCommonControl(); // Represents start/finish
if (k < nc && pc->getControl(k))
to = pc->getControl(k)->getId();
if (k>0 && pc->getControl(k-1))
from = pc->getControl(k-1)->getId();
for (size_t j = 0; j < ntimes; j++)
legToTime[256*from + to].push_back(splits[k][j]);
int time = 0;
if (ntimes < 5)
time = splits[k][0]; // Best time
else if (ntimes < 12)
time = (splits[k][0]+splits[k][1]) / 2; //Average best time
else {
int nval = ntimes/6;
for (int r = 1; r <= nval; r++)// "Best fraction", skip winner
time += splits[k][r];
time /= nval;
}
legRes.addTime(from, to, time);
legBestTime.addTime(from, to, splits[k][0]); // Add leader time
}
}
// Loop and sort times for each leg run in this class
for (map >::iterator cit = legToTime.begin(); cit != legToTime.end(); ++cit) {
int key = cit->first;
vector × = cit->second;
sort(times.begin(), times.end());
int jsiz = times.size();
for (int j = 0; j::iterator cit = cSet.begin(); cit != cSet.end(); ++cit) {
pCourse pc = *cit;
const unsigned nc = pc->getNumControls();
vector normRes(nc+1);
vector bestRes(nc+1);
int cc = pc->getCommonControl();
for (size_t k = 0; k <= nc; k++) {
int from = cc, to = cc; // Represents start/finish
if (k < nc && pc->getControl(k))
to = pc->getControl(k)->getId();
if (k>0 && pc->getControl(k-1))
from = pc->getControl(k-1)->getId();
normRes[k] = legRes.getTime(from, to);
bestRes[k] = legBestTime.getTime(from, to);
}
swap(tSplitAnalysisData[pc->getId()], normRes);
swap(tCourseLegLeaderTime[pc->getId()], bestRes);
}
}
bool oClass::isRogaining() const {
if (Course && Course->getMaximumRogainingTime() > 0)
return true;
if (MultiCourse.size() > 0 && MultiCourse[0].size() > 0 &&
MultiCourse[0][0] && MultiCourse[0][0]->getMaximumRogainingTime() > 0)
return true;
GeneralResult *gr = getResultModule();
// Return true if result module makes point calculations and
// controls define rogaining points. Otherwize point calculations
// is assumed to be just a side effect.
if (gr && gr->isRogaining() && Course) {
for (int n = 0; n < Course->getNumControls(); n++) {
pControl ctrl = Course->getControl(n);
if (ctrl && ctrl->isRogaining(true))
return true;
}
}
return false;
}
void oClass::remove()
{
if (oe)
oe->removeClass(Id);
}
bool oClass::canRemove() const
{
return !oe->isClassUsed(Id);
}
int oClass::getMaximumRunnerTime() const {
reinitialize(false);
return tMaxTime;
}
int oClass::getNumLegNoParallel() const {
int nl = 1;
for (size_t k = 1; k < legInfo.size(); k++) {
if (!legInfo[k].isParallel())
nl++;
}
return nl;
}
bool oClass::splitLegNumberParallel(int leg, int &legNumber, int &legOrder) const {
legNumber = 0;
legOrder = 0;
if (legInfo.empty())
return false;
int stop = min(leg, legInfo.size() - 1);
int k;
for (k = 0; k < stop; k++) {
if (legInfo[k+1].isParallel() || legInfo[k+1].isOptional())
legOrder++;
else {
legOrder = 0;
legNumber++;
}
}
if (legOrder == 0) {
if (k+1 < int(legInfo.size()) && (legInfo[k+1].isParallel() || legInfo[k+1].isOptional()))
return true;
if (!(legInfo[k].isParallel() || legInfo[k].isOptional()))
return false;
}
return true;
}
int oClass::getLegNumberLinear(int legNumberIn, int legOrderIn) const {
if (legNumberIn == 0 && legOrderIn == 0)
return 0;
int legNumber = 0;
int legOrder = 0;
for (size_t k = 1; k < legInfo.size(); k++) {
if (legInfo[k].isParallel() || legInfo[k].isOptional())
legOrder++;
else {
legOrder = 0;
legNumber++;
}
if (legNumberIn == legNumber && legOrderIn == legOrder)
return k;
}
return -1;
}
/** Return an actual linear index for this class. */
int oClass::getLinearIndex(int index, bool isLinear) const {
if (legInfo.empty())
return 0;
if (size_t(index) >= legInfo.size())
return legInfo.size() - 1; // -1 to last leg
return isLinear ? index : getLegNumberLinear(index, 0);
}
wstring oClass::getLegNumber(int leg) const {
int legNumber, legOrder;
bool par = splitLegNumberParallel(leg, legNumber, legOrder);
wchar_t bf[16];
if (par) {
char symb = 'a' + legOrder;
swprintf_s(bf, L"%d%c", legNumber + 1, symb);
}
else {
swprintf_s(bf, L"%d", legNumber + 1);
}
return bf;
}
oClass::ClassStatus oClass::getClassStatus() const {
if (tStatusRevision != oe->dataRevision) {
wstring s = getDCI().getString("Status");
if (s == L"I")
tStatus = ClassStatus::Invalid;
else if (s == L"IR")
tStatus = ClassStatus::InvalidRefund;
else
tStatus = ClassStatus::Normal;
tStatusRevision = oe->dataRevision;
}
return tStatus;
}
void oClass::fillClassStatus(vector> &statusClass) {
statusClass.push_back(make_pair(L"", L"OK"));
statusClass.push_back(make_pair(L"IR", L"Struken med återbetalning"));
statusClass.push_back(make_pair(L"I", L"Struken utan återbetalning"));
}
void oClass::clearCache(bool recalculate) {
if (recalculate)
oe->reCalculateLeaderTimes(getId());
clearSplitAnalysis();
tResultInfo.clear();//Do on competitor remove!
}
bool oClass::wasSQLChanged(int leg, int control) const {
if (oe->globalModification)
return true;
map >::const_iterator res = sqlChangedControlLeg.find(-1);
if (res != sqlChangedControlLeg.end()) {
if (leg == -1 || res->second.count(-1) || res->second.count(leg))
return true;
}
if (control != -1) {
if (control == -2) // Any control
return sqlChangedControlLeg.size() > 0;
res = sqlChangedControlLeg.find(control);
if (res != sqlChangedControlLeg.end()) {
if (leg == -1 || res->second.count(-1) || res->second.count(leg))
return true;
}
}
res = sqlChangedLegControl.find(leg);
if (res != sqlChangedLegControl.end()) {
if (control == -1 || res->second.count(-1) || res->second.count(control))
return true;
}
return false;
}
void oClass::markSQLChanged(int leg, int control) {
sqlChangedControlLeg[control].insert(leg);
sqlChangedLegControl[leg].insert(control);
oe->classChanged(this, false);
}
void oClass::changedObject() {
markSQLChanged(-1,-1);
tNoTiming = -1;
tIgnoreStartPunch = -1;
oe->sqlClasses.changed = true;
}
static void checkMissing(const map< pair, int > &master,
const map< pair, int > &check,
set< pair > &controlProblems) {
assert(master.size() >= check.size());
for ( map< pair, int >::const_iterator it = master.begin();
it != master.end(); ++it) {
map< pair, int >::const_iterator res = check.find(it->first);
if (res == check.end() || res->second != it->second)
controlProblems.insert(it->first);
}
}
// Check if forking is fair
bool oClass::checkForking(vector< vector > &legOrder,
vector< vector > &forks,
set< pair > &unfairLegs) const {
legOrder.clear();
forks.clear();
map hashes;
int max = 1;
set factors;
for (size_t k = 0; k < MultiCourse.size(); k++) {
if (MultiCourse[k].size() > 1 && factors.count(MultiCourse[k].size()) == 0) {
max *= MultiCourse[k].size();
factors.insert(MultiCourse[k].size()); // This is an over estimate. Should consider prime factors to get it exact
}
}
legOrder.reserve(max);
for (int k = 0; k < max; k++) {
vector order;
long long hash = 0;
for (size_t j = 0; j< MultiCourse.size(); j++) {
if (getLegType(j) == LTExtra || getLegType(j) == LTIgnore)
continue;
if (!MultiCourse[j].empty()) {
int ix = k % MultiCourse[j].size();
int cid = MultiCourse[j][ix]->getId();
order.push_back(cid);
hash = hash * 997 + cid;
}
}
if (order.empty())
continue;
if (hashes.count(hash) == 0) {
hashes[hash] = legOrder.size();
legOrder.push_back(order);
}
else {
int test = hashes[hash];
if (legOrder[test] != order) {
// Test for hash collision. Will not happen...
bool exist = false;
for (size_t i = 0; i < legOrder.size(); i++) {
if (legOrder[i] == order) {
exist = true;
break;
}
}
if (!exist) {
legOrder.push_back(order);
}
}
}
}
for (oTeamList::const_iterator it = oe->Teams.begin(); it != oe->Teams.end(); ++it) {
if (it->skip() || it->Class != this)
continue;
vector order;
bool valid = true;
long long hash = 0;
for (size_t j = 0; j < it->Runners.size(); j++) {
if (getLegType(j) == LTExtra || getLegType(j) == LTIgnore)
continue;
pCourse crs;
if (it->Runners[j] && (crs = it->Runners[j]->getCourse(false)) != 0) {
if (it->Runners[j]->getNumShortening() > 0) {
valid = false;
break;
}
int cid = crs->getId();
order.push_back(cid);
hash = hash * 997 + cid;
}
else {
valid = false;
break;
}
}
if (!valid)
continue;
if (hashes.count(hash) == 0) {
hashes[hash] = legOrder.size();
legOrder.push_back(order);
}
else {
int test = hashes[hash];
if (legOrder[test] != order) {
// Test for hash collision. Will not happen...
bool exist = false;
for (size_t i = 0; i < legOrder.size(); i++) {
if (legOrder[i] == order) {
exist = true;
break;
}
}
if (!exist) {
legOrder.push_back(order);
}
}
}
}
vector< vector > controlOrder(legOrder.size());
vector< map< pair, int > > countLegsPerFork(legOrder.size());
for(size_t k = 0; k < legOrder.size(); k++) {
for (size_t j = 0; j < legOrder[k].size(); j++) {
pCourse pc = oe->getCourse(legOrder[k][j]);
if (pc) {
controlOrder[k].push_back(-1); // Finish/start
for (int i = 0; i < pc->nControls; i++) {
int id = pc->Controls[i]->nNumbers == 1 ? pc->Controls[i]->Numbers[0] : pc->Controls[i]->getId();
controlOrder[k].push_back(id);
}
}
}
if (controlOrder[k].size() > 0)
controlOrder[k].push_back(-1); // Finish
for (size_t j = 1; j < controlOrder[k].size(); j++) {
int s = controlOrder[k][j-1];
int e = controlOrder[k][j];
++countLegsPerFork[k][make_pair(s, e)];
}
}
unfairLegs.clear();
for (size_t k = 1; k < countLegsPerFork.size(); k++) {
if (countLegsPerFork[0].size() >= countLegsPerFork[k].size())
checkMissing(countLegsPerFork[0], countLegsPerFork[k], unfairLegs);
else
checkMissing(countLegsPerFork[k], countLegsPerFork[0], unfairLegs);
}
forks = controlOrder;
return unfairLegs.empty();
}
long long oClass::setupForkKey(const vector indices, const vector< vector< vector > > &courseKeys, vector &legs) {
size_t sr = 0;
int mergeIx[maxRunnersTeam];
const vector *pCrs[maxRunnersTeam];
int npar = indices.size();
for (int k = 0; k < npar; k++) {
if (indices[k]>=0) {
pCrs[k] = &courseKeys[k][indices[k]];
sr += pCrs[k]->size();
}
else
pCrs[k] = 0;
mergeIx[k] = 0;
}
if (legs.size() <= sr)
legs.resize(sr+1);
size_t nleg = 0;
while (nleg < sr) {
int best = -1;
for (int i = 0; i < npar; i++) {
if (!pCrs[i])
continue;
int s = pCrs[i]->size();
if (mergeIx[i] < s && (best == -1 || (*pCrs[i])[mergeIx[i]] < (*pCrs[best])[mergeIx[best]]) )
best = i;
}
if (best == -1)
break;
legs[nleg++] = (*pCrs[best])[mergeIx[best]];
++mergeIx[best];
}
/*for (size_t k = 0; k < indices.size(); k++) {
if (indices[k]>=0) {
const vector &crs = courseKeys[k][indices[k]];
for (size_t j = 0; j < crs.size(); j++)
legs[nleg++] = crs[j];
//legs.insert(crs.begin(), crs.end());
//vector tl;
//tl.resize(legs.size() + crs.size());
//merge(legs.begin(), legs.end(), crs.begin(), crs.end(), tl.begin());
//tl.swap(legs);
}
}*/
/*for (size_t k = 0; k < indices.size(); k++) {
if (indices[k]>=0) {
const vector &crs = courseKeys[k][indices[k]];
//legs.insert(crs.begin(), crs.end());
vector tl;
tl.resize(legs.size() + crs.size());
merge(legs.begin(), legs.end(), crs.begin(), crs.end(), tl.begin());
tl.swap(legs);
}
}*/
unsigned long long key = 0;
for (size_t k = 0; k < nleg; k++)
key = key * 1057 + legs[k];
return key;
}
void maximizeSpread(const vector &coursesFirst, const vector& coursesLast, vector& order, int numToGenerate) {
struct Node {
map children;
vector courses;
int readIx = 0;
void insert(int ix, const pCourse& course, int position) {
if (course->getNumControls() == position)
courses.push_back(ix);
else {
children[course->getControl(position)->getId()].insert(ix, course, position + 1);
}
}
void insertBack(int ix, const pCourse& course, int position) {
if (course->getNumControls() == position)
courses.push_back(ix);
else {
children[course->getControl(course->getNumControls() - 1 - position)->getId()].insertBack(ix, course, position + 1);
}
}
void shuffleOrder() {
if (!courses.empty()) {
permute(courses);
}
for (auto c : children)
c.second.shuffleOrder();
}
int getNextForking(int inKey, unordered_map& controlUsage) {
if (readIx 1)
key = mKey + it->first;
else
key = inKey;
int best = controlUsage[it->first] + controlUsage[key];
auto next = it;
while (++it != children.end()) {
key = mKey + it->first;
int c = controlUsage[it->first] + controlUsage[key];
if (c < best) {
best = c;
next = it;
}
}
if (children.size() > 1)
key = mKey + next->first;
else
key = inKey;
int res = next->second.getNextForking(key, controlUsage);
if (res != -1) {
if (children.size() > 1)
++controlUsage[key];
++controlUsage[next->first];
return res;
}
// Empty
children.erase(next);
}
return -1;
}
};
Node courseOrderLast;
for (size_t i = 0; i < coursesLast.size(); i++)
courseOrderLast.insertBack(i, coursesLast[i], 0);
courseOrderLast.shuffleOrder();
unordered_map controlUsage;
order.resize(coursesLast.size());
for (int i = 0; i < order.size(); i++)
order[i] = courseOrderLast.getNextForking(0, controlUsage);
Node courseOrderFirst;
for (size_t i : order)
courseOrderFirst.insert(i, coursesFirst[i], 0);
controlUsage.clear();
int ns = min(coursesFirst.size(), numToGenerate);
order.resize(ns);
for (int i = 0; i < ns; i++)
order[i] = courseOrderFirst.getNextForking(0, controlUsage);
}
pair oClass::autoForking(const vector> &inputCourses, int numToGenerate) {
if (inputCourses.size() != getNumStages())
throw meosException("Internal error");
int legs = inputCourses.size();
vector nf(legs);
vector prod(legs);
vector ix(legs);
vector< vector< vector > > courseKeys(legs);
vector> pCourses(legs);
unsigned long long N = 1;
for (int k = 0; k < legs; k++) {
prod[k] = N;
if (!inputCourses[k].empty()) {
N *= inputCourses[k].size();
nf[k] = inputCourses[k].size();
}
// Setup course keys
courseKeys[k].resize(inputCourses[k].size());
for (size_t j = 0; j < inputCourses[k].size(); j++) {
pCourse pc = oe->getCourse(inputCourses[k][j]);
pCourses[k].push_back(pc);
if (pc) {
for (int c = 0; c <= pc->getNumControls(); c++) {
int from = c == 0 ? 1 : pc->getControl(c-1)->getId();
int to = c == pc->getNumControls() ? 2 : pc->getControl(c)->getId();
int key = from + to*997;
courseKeys[k][j].push_back(key);
}
sort(courseKeys[k][j].begin(), courseKeys[k][j].end());
}
}
}
// Sample if there are very many combinations.
uint64_t sampleFactor = 1;
while(N > 10000000) {
sampleFactor *= 13;
N /= 13;
}
size_t Ns = size_t(N);
map count;
vector ws;
for (size_t k = 0; k < Ns; k++) {
uint64_t D = uint64_t(k) * sampleFactor;
for (int j = 0; j < legs; j++) {
if (nf[j]>0) {
ix[j] = int((D/prod[j] + j) % nf[j]);
}
else ix[j] = -1;
}
unsigned long long key = setupForkKey(ix, courseKeys, ws);
++count[key];
}
// Select the key generating best forking
long long keyToUse = -1;
int mv = 0;
for (map::iterator it = count.begin(); it != count.end(); ++it) {
if (it->second > mv) {
keyToUse = it->first;
mv = it->second;
}
}
count.clear();
// Clear old forking
for (int j = 0; j < legs; j++) {
if (nf[j]>0) {
clearStageCourses(j);
}
}
set generatedForkKeys;
int genLimit = max(numToGenerate * 2, numToGenerate + 100);
vector> courseMatrix(legs);
for (size_t k = 0; k < Ns; k++) {
long long forkKey = 0;
for (int j = 0; j < legs; j++) {
uint64_t D = uint64_t((k * 997)%Ns) * sampleFactor;
if (nf[j]>0) {
ix[j] = int((D/prod[j] + j) % nf[j]);
forkKey = forkKey * 997 + ix[j];
}
else ix[j] = -1;
}
unsigned long long key = setupForkKey(ix, courseKeys, ws);
if (key == keyToUse && generatedForkKeys.count(forkKey) == 0) {
generatedForkKeys.insert(forkKey);
for (int j = 0; j < legs; j++) {
if (nf[j] > 0) {
// coursesUsed.insert(pCourses[j][ix[j]]->getId());
courseMatrix[j].push_back(pCourses[j][ix[j]]);
}
}
}
if (generatedForkKeys.size() >= genLimit)
break;
}
vector fperm;
for (size_t j = 0; j < courseMatrix.size(); j++) {
if (courseMatrix[j].empty())
continue;
int jj = courseMatrix.size() - 1;
while (courseMatrix[jj].empty())
jj--;
maximizeSpread(courseMatrix[j], courseMatrix[jj], fperm, numToGenerate);
// Take the first used course.
/*fperm.resize(courseMatrix[j].size());
for (size_t i = 0; i < fperm.size(); i++)
fperm[i] = i;*/
break;
}
// Determine first bib in class (if defined)
wstring bibInfo = getDCI().getString("Bib");
wchar_t pattern[32];
int firstNumber = extractBibPattern(bibInfo, pattern);
if (firstNumber == 0) {
// Not explicitly defined. Look at any teams
vector tl;
oe->getTeams(getId(), tl);
int minBib = 10000000;
int minSN = 10000000;
for (pTeam t : tl) {
t->getBib();
int n = extractBibPattern(bibInfo, pattern);
if (n > 0)
minBib = std::min(minBib, n);
int no = t->getStartNo();
if (no > 0)
minSN = std::min(minSN, no);
}
if (minBib > 0)
firstNumber = minBib;
else if (minSN > 0)
firstNumber = minSN;
}
unsigned int off = 0;
if (firstNumber > 0) {
// index = (index-1) % courses.size();
unsigned firstCourse = unsigned(firstNumber - 1) % fperm.size();
off = fperm.size() - firstCourse;
}
set coursesUsed;
int lastSet = -1;
for (int j = 0; j < legs; j++) {
if (nf[j] > 0) {
lastSet = j;
for (size_t k = 0; k < fperm.size(); k++) {
int kk = unsigned(k + off) % fperm.size();
coursesUsed.insert(courseMatrix[j][fperm[kk]]->getId());
addStageCourse(j, courseMatrix[j][fperm[kk]], -1);
}
}
else if (lastSet >= 0 && getLegType(j) == LTExtra) {
MultiCourse[j] = MultiCourse[lastSet];
}
else {
lastSet = -1;
}
}
return make_pair(fperm.size(), coursesUsed.size());
}
int oClass::extractBibPattern(const wstring &bibInfo, wchar_t pattern[32]) {
int number = 0;
if (bibInfo.empty())
pattern[0] = 0;
else {
number = 0;
int pIndex = 0;
bool hasNC = false;
for (size_t j = 0; j < bibInfo.size() && j < 10; j++) {
if (bibInfo[j]>='0' && bibInfo[j]<='9') {
if (!hasNC) {
pattern[pIndex++] = '%';
pattern[pIndex++] = 'd';
hasNC = true;
}
number = 10 * number + bibInfo[j]-'0';
}
else if (bibInfo[j] != '%' && bibInfo[j] > 32)
pattern[pIndex++] = bibInfo[j];
}
if (!hasNC) {
pattern[pIndex++] = '%';
pattern[pIndex++] = 'd';
number = 1;
}
pattern[pIndex] = 0;
}
return number;
}
AutoBibType oClass::getAutoBibType() const {
const wstring &bib = getDCI().getString("Bib");
if (bib.empty()) // Manual
return AutoBibManual;
else if (bib == L"*") // Consecutive
return AutoBibConsecutive;
else if (bib == L"-") // No bib
return AutoBibNone;
else
return AutoBibExplicit;
}
bool oClass::hasAnyCourse(const set &crsId) const {
if (Course && crsId.count(Course->getId()))
return true;
for (size_t j = 0; j < MultiCourse.size(); j++) {
for (size_t k = 0; k < MultiCourse[j].size(); k++) {
if (MultiCourse[j][k] && crsId.count(MultiCourse[j][k]->getId()))
return true;
}
}
return false;
}
bool oClass::usesCourse(const oCourse &crs) const {
if (Course == &crs)
return true;
for (size_t j = 0; j < MultiCourse.size(); j++) {
for (size_t k = 0; k < MultiCourse[j].size(); k++) {
if (MultiCourse[j][k] == &crs)
return true;
}
}
return false;
}
void oClass::extractBibPatterns(oEvent &oe, map > &patterns) {
vector t;
oe.getTeams(0, t, true);
vector r;
oe.getRunners(0, 0, r, true);
patterns.clear();
wchar_t pattern[32];
for (size_t k = t.size(); k > 0; k--) {
int cls = t[k-1]->getClassId(true);
if (cls == 0)
continue;
const wstring &bib = t[k-1]->getBib();
if (!bib.empty()) {
int num = extractBibPattern(bib, pattern);
if (num > 0) {
pair &val = patterns[cls];
if (num > val.second) {
val.second = num;
val.first = pattern;
}
}
}
}
for (size_t k = r.size(); k > 0; k--) {
if (r[k-1]->getTeam() != 0)
continue;
int cls = r[k-1]->getClassId(true);
if (cls == 0)
continue;
const wstring &bib = r[k-1]->getBib();
if (!bib.empty()) {
int num = extractBibPattern(bib, pattern);
if (num > 0) {
pair &val = patterns[cls];
if (num < val.second) {
val.second = num;
val.first = pattern;
}
}
}
}
}
pair oClass::getNextBib(map > &patterns) {
map >::iterator it = patterns.find(Id);
if (it != patterns.end() && it->second.second > 0) {
wchar_t bib[32];
swprintf_s(bib, it->second.first.c_str(), ++it->second.second);
return make_pair(it->second.second, bib);
}
return make_pair(0, _EmptyWString);
}
pair oClass::getNextBib() {
vector t;
oe->getTeams(Id, t, true);
set bibs;
wchar_t pattern[32];
if (!t.empty()) {
for (size_t k = 0; k < t.size(); k++) {
const wstring &bib = t[k]->getBib();
if (!bib.empty()) {
int num = extractBibPattern(bib, pattern);
if (num > 0) {
bibs.insert(num);
}
}
}
}
else {
vector r;
oe->getRunners(Id, 0, r, true);
for (size_t k = 0; k < r.size(); k++) {
if (r[k]->getTeam() != 0)
continue;
const wstring &bib = r[k]->getBib();
if (!bib.empty()) {
int num = extractBibPattern(bib, pattern);
if (num > 0) {
bibs.insert(num);
}
}
}
}
if (bibs.empty()) {
wstring bibInfo = getDCI().getString("Bib");
int firstNumber = extractBibPattern(bibInfo, pattern);
if (firstNumber > 0)
return make_pair(firstNumber, bibInfo);
return make_pair(0, _EmptyWString);
}
int candidate = -1;
for (set::iterator it = bibs.begin(); it != bibs.end(); ++it) {
if (candidate > 0 && *it != candidate) {
break;
}
candidate = *it + 1;
}
wchar_t bib[32];
swprintf_s(bib, pattern, candidate);
return make_pair(candidate, bib);
}
int oClass::getNumForks() const {
set factors;
for (size_t k = 0; k < MultiCourse.size(); k++) {
int f = MultiCourse[k].size();
if (f <= 1)
continue;
bool skip = false;
for (set::iterator it = factors.begin(); it != factors.end(); ++it) {
int of = *it;
if (of == f) {
skip = true;
break;
}
else if (f < of && (of % f) == 0) {
skip = true;
break;
}
else if (of < f && (f % of) == 0) {
factors.erase(it);
it = factors.begin();
continue;
}
}
if (!skip)
factors.insert(f);
}
int res = 1;
for (set::iterator it = factors.begin(); it != factors.end(); ++it) {
res *= *it;
}
return res;
}
void oClass::getSeedingMethods(vector< pair > &methods) {
methods.clear();
methods.push_back(make_pair(lang.tl("Resultat"), SeedResult));
methods.push_back(make_pair(lang.tl("Tid"), SeedTime));
methods.push_back(make_pair(lang.tl("Ranking"), SeedRank));
methods.push_back(make_pair(lang.tl("Poäng"), SeedPoints));
}
void oClass::drawSeeded(ClassSeedMethod seed, int leg, int firstStart,
int interval, const vector &groups,
bool noClubNb, bool reverseOrder, int pairSize) {
vector r;
oe->getRunners(Id, 0, r, true);
vector< pair > seedIx;
if (seed == SeedResult) {
oe->reEvaluateAll(set(), true);
oe->calculateResults({}, oEvent::ResultType::ClassResult, false);
}
for (size_t k = 0; k < r.size(); k++) {
if (r[k]->tLeg != leg && leg != -1)
continue;
pair sx;
sx.second = k;
if (seed == SeedRank)
sx.first = r[k]->getRanking();
else if (seed == SeedResult)
sx.first = ClassSplit::evaluateResult(*r[k]);
else if (seed == SeedTime)
sx.first = ClassSplit::evaluateTime(*r[k]);
else if (seed == SeedPoints)
sx.first = ClassSplit::evaluatePoints(*r[k]);
else
throw meosException("Not yet implemented");
seedIx.push_back(sx);
}
sort(seedIx.begin(), seedIx.end());
if (seedIx.empty() || seedIx.front().first == seedIx.back().first) {
throw meosException("error:invalidmethod");
}
vector seedSpec;
if (groups.size() == 1) {
size_t added = 0;
if (groups[0] <= 0)
throw meosException("Internal error");
while (added < seedIx.size()) {
seedSpec.push_back(groups[0]);
added += groups[0];
}
}
else {
size_t added = 0;
for (size_t k = 0; k < groups.size(); k++) {
if (groups[k] <= 0)
throw meosException("Internal error");
seedSpec.push_back(groups[k]);
added += groups[k];
}
if (added < seedIx.size())
seedSpec.push_back(seedIx.size() - added);
}
list< vector > seedGroups(1);
size_t groupIx = 0;
for (size_t k = 0; k < seedIx.size(); ) {
if (groupIx < seedSpec.size() && seedGroups.back().size() >= seedSpec[groupIx]) {
groupIx++;
seedGroups.push_back(vector());
}
int value = seedIx[k].first;
while (k < seedIx.size() && seedIx[k].first == value) {
seedGroups.back().push_back(seedIx[k].second);
k++;
}
}
vector startOrder;
for (list< vector >::iterator it = seedGroups.begin();
it != seedGroups.end(); ++it) {
vector &g = *it;
permute(g);
for (size_t k = 0; k < g.size(); k++) {
startOrder.push_back(r[g[k]]);
}
}
if (noClubNb) {
set pushed_back;
for (int k = 1; k < startOrder.size(); k++) {
int idMe = startOrder[k]->getClubId();
if (idMe != 0 && idMe == startOrder[k-1]->getClubId()) {
// Make sure the runner with worst ranking is moved back. (Swedish SM rules)
bool skipRank = pushed_back.count(startOrder[k - 1]->getId()) != 0;
if (!skipRank && startOrder[k-1]->getRanking() > startOrder[k]->getRanking())
swap(startOrder[k-1], startOrder[k]);
pushed_back.insert(startOrder[k]->getId());
vector > rqueue;
rqueue.push_back(make_pair(k, startOrder[k]));
for (int j = k + 1; j < startOrder.size(); j++) {
if (idMe != startOrder[j]->getClubId()) {
pushed_back.insert(startOrder[j]->getId());
swap(startOrder[j], startOrder[k]); // k-1 now has a non-club nb behind
rqueue.push_back(make_pair(j, nullptr));
// Shift the queue
for (size_t q = 1; q < rqueue.size(); q++) {
startOrder[rqueue[q].first] = rqueue[q-1].second;
}
break;
}
else {
rqueue.push_back(make_pair(j, startOrder[j]));
}
}
/*for (size_t j = k + 1; j < startOrder.size(); j++) {
swap(startOrder[j], startOrder[j-1]);
if (idMe != startOrder[j]->getClubId() && j+1 < startOrder.size() &&
idMe != startOrder[j+1]->getClubId()) {
break;
}
}*/
}
}
// Handle special case where the two last have same club.
int last = startOrder.size() - 1;
if (last >= 3) {
int lastClub = startOrder[last]->getClubId();
if ( lastClub == startOrder[last-1]->getClubId() &&
lastClub != startOrder[last-2]->getClubId() &&
lastClub != startOrder[last-3]->getClubId() ) {
swap(startOrder[last-1], startOrder[last-2]);
}
}
}
if (!reverseOrder)
reverse(startOrder.begin(), startOrder.end());
for (size_t k = 0; k < startOrder.size(); k++) {
int kx = k/pairSize;
startOrder[k]->setStartTime(firstStart + interval * kx, true, oBase::ChangeType::Update, false);
startOrder[k]->synchronize(true);
}
}
bool oClass::hasClassGlobalDependence() const {
for (size_t k = 0; k < legInfo.size(); k++) {
if (legInfo[k].startMethod == STPursuit)
return true;
}
return false;
}
int oClass::getDrawFirstStart() const {
return getDCI().getInt("FirstStart");
}
void oClass::setDrawFirstStart(int st) {
getDI().setInt("FirstStart", st);
}
int oClass::getDrawInterval() const {
return getDCI().getInt("StartInterval");
}
void oClass::setDrawInterval(int st) {
getDI().setInt("StartInterval", st);
}
int oClass::getDrawVacant() const {
return getDCI().getInt("Vacant");
}
void oClass::setDrawVacant(int st) {
getDI().setInt("Vacant", st);
}
int oClass::getDrawNumReserved() const {
return getDCI().getInt("Reserved") & 0xFF;
}
void oClass::setDrawNumReserved(int st) {
int v = getDCI().getInt("Reserved") & 0xFF00;
getDI().setInt("Reserved", v|st);
}
void oClass::setDrawSpecification(const vector &spec) {
int flag = 0;
for (auto ds : spec) {
flag |= int(ds);
}
int v = getDrawNumReserved();
getDI().setInt("Reserved", v | (flag<<8));
}
set oClass::getDrawSpecification() const {
int v = (getDCI().getInt("Reserved") & 0xFF00) >> 8;
set