/************************************************************************
MeOS - Orienteering Software
Copyright (C) 2009-2021 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
************************************************************************/
#include "StdAfx.h"
#include "qualification_final.h"
#include "meos_util.h"
#include "meosexception.h"
#include
#include
#include "xmlparser.h"
#include "oRunner.h"
#include "localizer.h"
pair QualificationFinal::getNextFinal(int instance, int orderPlace, int numSharedPlaceNext) const {
pair key(instance, orderPlace);
int iter = 0;
while (numSharedPlaceNext >= 0) {
auto res = sourcePlaceToFinalOrder.find(key);
if (res != sourcePlaceToFinalOrder.end()) {
if (iter >= 2) { // For three in a shared last place
pair key2 = key;
int extraSub = ((iter + 1) % 2);
key2.second -= extraSub;
auto res2 = sourcePlaceToFinalOrder.find(key2);
if (res2 != sourcePlaceToFinalOrder.end()) {
auto ans = res2->second;
ans.second += iter + extraSub;
return ans;
}
}
auto ans = res->second;
ans.second += iter;
return ans;
}
--key.second;
--numSharedPlaceNext;
++iter;
}
return make_pair(0, -1);
}
bool QualificationFinal::noQualification(int instance) const {
if (size_t(instance) >= classDefinition.size())
return false;
return classDefinition[instance].qualificationMap.empty() &&
classDefinition[instance].timeQualifications.empty();
}
void QualificationFinal::getBaseClassInstances(set &base) const {
for (size_t k = 0; k < classDefinition.size(); k++) {
if (noQualification(k))
base.insert(k+1);
else break;
}
}
void QualificationFinal::import(const wstring &file) {
xmlparser xml;
xml.read(file);
auto qr = xml.getObject("QualificationRules");
xmlList levels;
qr.getObjects("Level", levels);
map idToIndex;
map > qualificationRelations;
int numBaseLevels = 0;
int iLevel = 0;
for (size_t iLevel = 0; iLevel < levels.size(); iLevel++) {
auto &level = levels[iLevel];
wstring rankS;
level.getObjectString("distribution", rankS);
bool rankSort = false;
if (rankS == L"Ranking")
rankSort = true;
else if (!rankS.empty())
throw meosException(L"Unknown distribution: " + rankS);
xmlList classes;
level.getObjects("Class", classes);
for (auto &cls : classes) {
wstring name;
cls.getObjectString("Name", name);
if (name.empty())
throw meosException("Klassen måste ha ett namn.");
int classId = cls.getObjectInt("id");
if (!(classId>0))
throw meosException("Id must be a positive integer.");
if (idToIndex.count(classId))
throw meosException("Duplicate class with id " + itos(classId));
xmlList rules;
cls.getObjects("Qualification", rules);
if (rules.empty())
numBaseLevels = 1; // Instance zero is not used as qualification,
idToIndex[classId] = classDefinition.size() + numBaseLevels;
classDefinition.push_back(Class());
classDefinition.back().name = name;
classDefinition.back().rankLevel = rankSort;
for (auto &qf : rules) {
int place = qf.getObjectInt("place");
if (place > 0) {
int id = qf.getObjectInt("id");
if (id == 0 && iLevel == 0)
classDefinition.back().qualificationMap.push_back(make_pair(0, place));
else if (idToIndex.count(id)) {
classDefinition.back().qualificationMap.push_back(make_pair(idToIndex[id], place));
qualificationRelations[classId].insert(id);
}
else
throw meosException("Unknown class with id " + itos(id));
}
else {
string time;
qf.getObjectString("place", time);
if (time == "time") {
string ids;
qf.getObjectString("id", ids);
vector vid;
split(ids, ",;", vid);
vector ivid;
for (auto &s : vid) {
int i = atoi(s.c_str());
if (!idToIndex.count(i))
throw meosException("Unknown class with id " + itos(i));
ivid.push_back(idToIndex[i]);
}
if (ivid.empty())
throw meosException(L"Empty time qualification for " + name);
classDefinition.back().timeQualifications.push_back(ivid);
}
else throw meosException("Unknown classification rule " + time);
}
}
}
}
/*classDefinition.resize(3);
classDefinition[0].name = L"Semi A";
classDefinition[1].name = L"Semi B";
classDefinition[2].name = L"Final";
for (int i = 0; i < 4; i++) {
classDefinition[0].qualificationMap.push_back(make_pair(0, i * 2 + 1));
classDefinition[1].qualificationMap.push_back(make_pair(0, i * 2 + 2));
classDefinition[2].qualificationMap.push_back(make_pair(i%2+1, i/2 + 1));
}
*/
initgmap(true);
}
void QualificationFinal::init(const wstring &def) {
serializedFrom = def;
vector races, rtdef, rdef;
split(def, L"|", races);
classDefinition.resize(races.size());
bool valid = true;
bool first = true;
for (size_t k = 0; k < races.size(); k++) {
bool rankLevel = false;
if (races[k].size() > 0 && races[k][0] == 'R') {
rankLevel = true;
races[k] = races[k].substr(1);
}
split(races[k], L"T", rtdef);
classDefinition[k].qualificationMap.clear();
classDefinition[k].timeQualifications.clear();
classDefinition[k].rankLevel = rankLevel;
if (first && rtdef.empty())
continue;
valid = rtdef.size() > 0;
if (!valid)
break;
first = false;
split(rtdef[0], L";", rdef);
valid = rdef.size() > 0 && rdef.size()%2 == 0;
if (!valid)
break;
for (size_t j = 0; j < rdef.size(); j+=2) {
size_t src = _wtoi(rdef[j].c_str());
if (src > k) {
valid = false;
break;
}
const wstring &rd = rdef[j + 1];
size_t d1 = _wtoi(rd.c_str());
size_t d2 = d1;
size_t range = rd.find_first_of('-', 0);
if (range < rd.size())
d2 = _wtoi(rd.c_str()+range+1);
if (d1 > d2) {
valid = false;
break;
}
while (d1 <= d2) {
classDefinition[k].qualificationMap.push_back(make_pair(int(src), int(d1)));
d1++;
}
}
for (size_t i = 1; valid && i < rtdef.size(); i++) {
split(rtdef[i], L";", rdef);
classDefinition[k].timeQualifications.push_back(vector());
for (size_t j = 0; valid && j < rdef.size(); j += 2) {
size_t src = _wtoi(rdef[j].c_str());
if (src > k) {
valid = false;
break;
}
classDefinition[k].timeQualifications.back().push_back(src);
}
}
}
if (!valid)
classDefinition.clear();
initgmap(false);
}
void QualificationFinal::encode(wstring &output) const {
output.clear();
for (size_t k = 0; k < classDefinition.size(); k++) {
if (k > 0)
output.append(L"|");
if (classDefinition[k].rankLevel)
output.append(L"R");
auto &qm = classDefinition[k].qualificationMap;
for (size_t j = 0; j < qm.size(); j++) {
if (j > 0)
output.append(L";");
size_t i = j;
while ((i + 1) < qm.size() && qm[i + 1].first == qm[i].first
&& qm[i + 1].second == qm[i].second+1) {
i++;
}
output.append(itow(qm[j].first) + L";");
if (i <= j + 1) {
output.append(itow(qm[j].second));
}
else {
output.append(itow(qm[j].second) + L"-" + itow(qm[i].second));
j = i;
}
}
auto &tqm = classDefinition[k].timeQualifications;
for (auto &source : tqm) {
output.append(L"T");
for (size_t i = 0; i < source.size(); i++) {
if (i > 0)
output.append(L";");
output.append(itow(source[i]));
}
}
}
serializedFrom = output;
}
int QualificationFinal::getNumStages(int stage) const {
if (stage == 0 || classDefinition[stage-1].qualificationMap.empty())
return 1;
set races;
for (auto &qm : classDefinition[stage - 1].qualificationMap) {
if (qm.first == stage)
throw meosException("Invalid qualification scheme");
races.insert(qm.first);
}
int def = 0;
for (int r : races)
def = max(def, 1 + getNumStages(r));
return def;
}
void QualificationFinal::initgmap(bool check) {
sourcePlaceToFinalOrder.clear();
levels.resize(getNumLevels(), LevelInfo::Normal);
for (int ix = 0; ix < (int)classDefinition.size(); ix++) {
auto &c = classDefinition[ix];
if (c.rankLevel)
levels[getLevel(ix+1)] = LevelInfo::RankSort;
for (int k = 0; k < (int)c.qualificationMap.size(); k++) {
const pair &sd = c.qualificationMap[k];
if (check && sourcePlaceToFinalOrder.count(sd))
throw meosException(L"Inconsistent qualification rule, X#" + c.name + + L"/" + itow(sd.first));
sourcePlaceToFinalOrder[sd] = make_pair(ix+1, k);
}
}
}
int QualificationFinal::getLevel(int instance) const {
if (instance == 0)
return 0;
instance--;
if (classDefinition[instance].level >= 0)
return classDefinition[instance].level;
int level = 0;
int src = instance;
while (!classDefinition[instance].qualificationMap.empty()) {
instance = classDefinition[instance].qualificationMap.front().first - 1;
if (instance < 0)
break;
level++;
if (size_t(level) > classDefinition.size())
throw meosException("Internal error");
}
classDefinition[src].level = level;
return level;
}
bool QualificationFinal::isFinalClass(int instance) const {
return instance > 0 && sourcePlaceToFinalOrder.count(make_pair(instance, 1)) == 0;
}
int QualificationFinal::getNumLevels() const {
return getLevel(classDefinition.size() - 1) + 1;
}
int QualificationFinal::getMinInstance(int level) const {
int minInst = classDefinition.size() - 1;
for (int i = classDefinition.size() - 1; i >= 0; i--) {
if (i == 0 && minInst == 1)
break; // No need to include base instance.
if (getLevel(i) == level)
minInst = i;
}
return minInst;
}
int QualificationFinal::getHeatFromClass(int finalClassId, int baseClassId) const {
if (baseClassId == baseId) {
int fci = (finalClassId - baseId) / maxClassId;
if (fci * maxClassId + baseClassId == finalClassId)
return fci;
else
return -1;
}
return 0;
}
/** Clear previous staus data*/
void QualificationFinal::prepareCalculations() {
storedInfoLookup.clear();
storedInfo.clear();
}
/** Retuns the final class and the order within that class. */
void QualificationFinal::setupNextFinal(pRunner r, int instance, int orderPlace, int numSharedPlaceNext) {
storedInfo.resize(getNumClasses());
pair res = getNextFinal(instance, orderPlace, numSharedPlaceNext);
int level = getLevel(instance);
storedInfo[level].emplace_back(r, res.first, res.second);
}
/** Do internal calculations. */
void QualificationFinal::computeFinals() {
if (storedInfo.empty())
return;
int nl = getNumLevels();
for (int level = 1; level < nl; level++) {
vector placeCount(classDefinition.size(), 0);
if (levels[level] == LevelInfo::RankSort) {
vector< pair > rankIx;
vector< pair > placeQueue;
set numClassCount;
auto &si = storedInfo[level-1];
if (si.empty())
continue;
rankIx.reserve(si.size());
placeQueue.reserve(si.size());
for (int i = 0; size_t(i) < si.size(); i++) {
if (si[i].instance > 0) {
int rank = si[i].r->getRanking();
rankIx.emplace_back(rank, i);
placeQueue.emplace_back(si[i].order, si[i].instance);
numClassCount.insert(si[i].instance);
}
}
int numClass = numClassCount.size();
vector rotmap;
if (numClass == 2) {
rotmap = { 1,0 };
}
else if (numClass == 3) {
rotmap = { 2,0,1 };
}
else if (numClass == 4) {
rotmap = { 3, 2, 0, 1 };
}
else if (numClass == 5) {
rotmap = { 4, 3, 0, 2, 1 };
}
else if (numClass == 6) {
rotmap = { 5, 4, 3, 0, 2, 1 };
}
else {
rotmap.resize(numClass);
for (int k = 0; k < numClass; k++)
rotmap[k] = (k + 1) % numClass;
}
sort(rankIx.begin(), rankIx.end());
sort(placeQueue.begin(), placeQueue.end());
vector placementOrder;
for (size_t ix = 0; ix < placeQueue.size(); ix++) {
placementOrder.push_back(ix);
}
for (size_t i = numClass; i + numClass <= placementOrder.size(); i += numClass * 2) {
reverse(placementOrder.begin() + i, placementOrder.begin() + i + numClass);
}
reverse(placementOrder.begin(), placementOrder.end());
/*
vector work;
int ix = placeQueue.size() - 1;
int iteration = 0;
bool versionA = false;
while (ix>=0) {
work.clear();
for (int i = 0; i < numClass && ix >= 0; i++) {
work.push_back(ix--);
}
if (versionA) {
if (work.size() == numClass)
rotate(work.begin(), work.begin() + iteration, work.end());
iteration = rotmap[iteration];
}
else {
if (iteration % 2 == 1)
reverse(work.begin(), work.end());
iteration++;
}
for (size_t i = 1; i <= work.size(); i++) {
placementOrder[ix + i] = work[work.size() - i];
}
}*/
for (auto &rankRes : rankIx) {
si[rankRes.second].instance = placeQueue[placementOrder.back()].second;
si[rankRes.second].order = placeQueue[placementOrder.back()].first;
placementOrder.pop_back();
}
}
}
for (auto & si : storedInfo) {
for (auto &res : si) {
storedInfoLookup[res.r->getId()] = &res;
}
}
}
/** Retuns the final class and the order within that class. */
pair QualificationFinal::getNextFinal(int runnerId) const {
auto res = storedInfoLookup.find(runnerId);
if (res == storedInfoLookup.end()) {
return make_pair(0, -1);
}
return make_pair(res->second->instance, res->second->order);
}
void QualificationFinal::printScheme(oClass * cls, gdioutput &gdi) const {
vector cname;
// vector