meos-2024/code/oevent_transfer.cpp
2020-08-01 22:55:57 +02:00

1474 lines
43 KiB
C++

/************************************************************************
MeOS - Orienteering Software
Copyright (C) 2009-2020 Melin Software HB
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License fro more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Melin Software HB - software@melin.nu - www.melin.nu
Eksoppsvägen 16, SE-75646 UPPSALA, Sweden
************************************************************************/
#include "stdafx.h"
#include <vector>
#include <set>
#include <cassert>
#include <algorithm>
#include <limits>
#include <random>
#include "oEvent.h"
#include "oDataContainer.h"
#include "metalist.h"
#include "generalresult.h"
#include "meosException.h"
#include "meos.h"
#include "meos_util.h"
#include "intkeymapimpl.hpp"
#include "MeOSFeatures.h"
void oEvent::merge(oEvent &src, int &numAdd, int &numRemove, int &numUpdate) {
numAdd = 0;
numRemove = 0;
numUpdate = 0;
wstring mergeTag = src.getMergeTag();
wstring mergeTime = getMergeInfo(mergeTag);
wstring baseTime, reverseMerge;
string addMinTime = src.getLastModified();
if (src.currentNameId == currentNameId) {
// Get base version time
if (baseTime.empty()) {
baseTime = src.getDCI().getString("ImportStamp");
if (baseTime.empty())
baseTime = getDCI().getString("ImportStamp");
}
reverseMerge = src.getMergeInfo(getMergeTag());
if (reverseMerge.empty())
reverseMerge = baseTime;
}
if (mergeTime.empty())
mergeTime = baseTime;
string previousMergeTime(mergeTime.begin(), mergeTime.end());
string thisMergeTime;
string bt(reverseMerge.begin(), reverseMerge.end());
set<int> rControl, rRunner, rTeam, rCourse, rClub, rClass;
auto updateNewItem = [&addMinTime, &numAdd](oBase *pNew, const oBase &src) {
if (pNew) {
numAdd++;
pNew->merge(src);
pNew->synchronize();
if (pNew->Modified.getStamp() < addMinTime)
pNew->Modified.setStamp(addMinTime);
}
};
auto mergeItem = [&numUpdate](oBase *pExisting, const oBase &src) {
numUpdate++;
string oldStamp = pExisting->Modified.getStamp();
pExisting->merge(src);
if (pExisting->Modified.getStamp() < oldStamp)
pExisting->Modified.setStamp(oldStamp);
};
auto computeRemove = [&bt](const auto &list, const set<int> &existing, set<int> &remove) {
for (auto &c : list) {
if (!c.isRemoved() && !existing.count(c.Id) && c.getStamp() < bt)
remove.insert(c.Id);
}
};
{
map<int, pControl> ctrl;
for (oControl &c : Controls) {
if (!c.isRemoved())
ctrl[c.Id] = &c;
}
set<int> srcControl;
for (const oControl &c : src.Controls) {
const string &stmp = c.getStamp();
if (!c.isRemoved()) {
if (stmp > previousMergeTime) {
if (stmp > thisMergeTime)
thisMergeTime = stmp;
auto mc = ctrl.find(c.Id);
if (mc != ctrl.end()) {
mergeItem(mc->second, c);
}
else {
pControl pNew = addControl(c);
updateNewItem(pNew, c);
}
}
srcControl.insert(c.Id);
}
}
computeRemove(Controls, srcControl, rControl);
}
{
map<int, pCourse> crs;
for (oCourse &c : Courses) {
if (!c.isRemoved())
crs[c.Id] = &c;
}
set<int> srcCourse;
for (const oCourse &c : src.Courses) {
const string &stmp = c.getStamp();
if (!c.isRemoved()) {
bool okMerge = stmp > previousMergeTime;
if (stmp > thisMergeTime)
thisMergeTime = stmp;
auto mc = crs.find(c.Id);
if (mc != crs.end()) {
if (okMerge)
mergeItem(mc->second, c);
}
else if (okMerge) {
pCourse pNew = addCourse(c);
updateNewItem(pNew, c);
}
srcCourse.insert(c.Id);
}
}
computeRemove(Courses, srcCourse, rCourse);
}
{
map<int, pClass> cls;
map<wstring, pClass> clsN;
for (oClass &c : Classes) {
if (!c.isRemoved()) {
cls[c.Id] = &c;
clsN[c.Name] = &c;
}
}
set<int> srcClass;
for (oClass &c : src.Classes) {
const string &stmp = c.getStamp();
bool merged = false;
if (!c.isRemoved()) {
bool okMerge = stmp > previousMergeTime;
if (stmp > thisMergeTime)
thisMergeTime = stmp;
auto mc = cls.find(c.Id);
if (mc != cls.end()) {
if (compareClassName(mc->second->Name, c.Name)) {
if (okMerge)
mergeItem(mc->second, c);
merged = true;
}
}
auto updateIdCls = [&](int id) {
pClass other = src.getClass(id);
if (other)
other->changeId(c.Id);
c.changeId(id);
};
if (!merged) {
auto mcN = clsN.find(c.Name);
if (mcN != clsN.end()) {
if (okMerge)
mergeItem(mcN->second, c);
merged = true;
updateIdCls(mcN->second->Id);
}
}
if (!merged && okMerge) {
if (cls.count(c.Id)) {
int newId = max(getFreeClassId(), src.getFreeClassId());
c.changeId(newId);
}
pClass pNew = addClass(c);
updateNewItem(pNew, c);
}
srcClass.insert(c.Id);
}
}
computeRemove(Classes, srcClass, rClass);
}
{ // Removing card not supported --> maybe too riskful...
map<int, pCard> crd;
map<pair<int, int>, pCard> crdByHash;
for (oCard &c : Cards) {
if (!c.isRemoved()) {
crd[c.Id] = &c;
crdByHash[c.getCardHash()] = &c;
}
}
for (oCard &c : src.Cards) {
const string &stmp = c.getStamp();
if (!c.isRemoved() && stmp > previousMergeTime) {
if (stmp > thisMergeTime)
thisMergeTime = stmp;
bool merged = false;
auto mc = crd.find(c.Id);
if (mc != crd.end()) {
auto p1 = c.getNumPunches() > 0 ? c.getPunchByIndex(c.getNumPunches() - 1)->getTimeInt() : 0;
auto c2 = mc->second;
auto p2 = c2->getNumPunches() > 0 ? c2->getPunchByIndex(c2->getNumPunches() - 1)->getTimeInt() : 0;
if (p1 == p2)
mergeItem(mc->second, c), merged = true;
}
auto updateIdCrd = [&](int id) {
pCard other = src.getCard(id);
if (other)
other->changeId(c.Id);
c.changeId(id);
};
if (!merged) {
auto mcN = crdByHash.find(c.getCardHash());
if (mcN != crdByHash.end()) {
mergeItem(mcN->second, c);
merged = true;
updateIdCrd(mcN->second->Id);
}
}
if (!merged) {
if (crd.count(c.Id)) {
int newId = max(getFreeCardId(), src.getFreeCardId());
c.changeId(newId);
}
pCard pNew = addCard(c);
updateNewItem(pNew, c);
}
}
}
}
{
map<int, pClub> clb;
map<int64_t, pClub> clbByExt;
map<wstring, pClub> clbByName;
for (oClub &c : Clubs) {
if (!c.isRemoved()) {
clb[c.Id] = &c;
if (c.getExtIdentifier() != 0)
clbByExt[c.getExtIdentifier()] = &c;
clbByName[c.getName()] = &c;
}
}
set<int> srcClub;
for (oClub &c : src.Clubs) {
const string &stmp = c.getStamp();
if (!c.isRemoved()) {
bool okMerge = stmp > previousMergeTime;
if (stmp > thisMergeTime)
thisMergeTime = stmp;
bool merged = false;
auto mc = clb.find(c.Id);
if (mc != clb.end()) {
if ((c.getExtIdentifier() != 0 && c.getExtIdentifier() == mc->second->getExtIdentifier())
|| c.getName() == mc->second->getName()) {
if (okMerge)
mergeItem(mc->second, c);
merged = true;
}
}
auto updateIdClb = [&](int id) {
pClub other = src.getClub(id);
if (other)
other->changeId(c.Id);
c.changeId(id);
};
if (!merged && c.getExtIdentifier() != 0) {
auto mcN = clbByExt.find(c.getExtIdentifier());
if (mcN != clbByExt.end()) {
if (okMerge)
mergeItem(mcN->second, c);
merged = true;
updateIdClb(mcN->second->Id);
}
}
if (!merged) {
auto mcN = clbByName.find(c.getName());
if (mcN != clbByName.end()) {
if (okMerge)
mergeItem(mcN->second, c);
merged = true;
updateIdClb(mcN->second->Id);
}
}
if (!merged && okMerge) {
if (clb.count(c.Id)) {
int newId = max(getFreeClubId(), src.getFreeClubId());
c.changeId(newId);
}
pClub pNew = addClub(c);
updateNewItem(pNew, c);
}
srcClub.insert(c.Id);
}
}
computeRemove(Clubs, srcClub, rClub);
}
{
map<int, pRunner> rn;
map<int64_t, pRunner> rnByExt;
map<pair<int, wstring>, pRunner> rnByCardName;
for (oRunner &r : Runners) {
if (!r.isRemoved()) {
rn[r.Id] = &r;
if (r.getExtIdentifier() != 0)
rnByExt[r.getExtIdentifier()] = &r;
rnByCardName[make_pair(r.getCardNo(), r.sName)] = &r;
}
}
set<int> srcRunner;
for (oRunner &r : src.Runners) {
const string &stmp = r.getStamp();
if (!r.isRemoved()) {
bool okMerge = stmp > previousMergeTime;
if (stmp > thisMergeTime)
thisMergeTime = stmp;
bool merged = false;
auto mc = rn.find(r.Id);
if (mc != rn.end()) {
if ((r.getExtIdentifier() != 0 && r.getExtIdentifier() == mc->second->getExtIdentifier())
|| (r.sName == mc->second->sName && r.getClubId() == mc->second->getClubId())
|| r.getCardNo() == mc->second->getCardNo()
|| mc->second->isVacant()) {
if (okMerge)
mergeItem(mc->second, r);
merged = true;
}
}
auto updateIdR = [&](int id) {
pRunner other = src.getRunner(id, 0);
if (other)
other->changeId(src.Id);
r.changeId(id);
};
if (!merged && r.getExtIdentifier() != 0) {
auto mcN = rnByExt.find(r.getExtIdentifier());
if (mcN != rnByExt.end()) {
if (okMerge)
mergeItem(mcN->second, r);
merged = true;
updateIdR(mcN->second->Id);
}
}
if (!merged) {
auto mcN = rnByCardName.find(make_pair(r.getCardNo(), r.sName));
if (mcN != rnByCardName.end()) {
if (okMerge)
mergeItem(mcN->second, r);
merged = true;
updateIdR(mcN->second->Id);
}
}
if (!merged && okMerge) {
if (rn.count(r.Id)) {
int newId = max(getFreeRunnerId(), src.getFreeRunnerId());
r.changeId(newId);
}
pRunner pNew = addRunner(r, false);
updateNewItem(pNew, r);
}
srcRunner.insert(r.Id);
}
}
computeRemove(Runners, srcRunner, rRunner);
}
{
map<int, pTeam> tm;
map<pair<int, wstring>, pTeam> tmByClassName;
for (oTeam &t : Teams) {
if (!t.isRemoved()) {
tm[t.Id] = &t;
tmByClassName[make_pair(t.getClassId(false), t.getName())] = &t;
}
}
set<int> srcTeam;
for (oTeam &t : src.Teams) {
const string &stmp = t.getStamp();
if (!t.isRemoved()) {
bool okMerge = stmp > previousMergeTime;
if (stmp > thisMergeTime)
thisMergeTime = stmp;
bool merged = false;
auto mc = tm.find(t.Id);
if (mc != tm.end()) {
if (t.getClubId() == mc->second->getClubId()) {
if (okMerge)
mergeItem(mc->second, t);
merged = true;
}
}
auto updateIdT = [&](int id) {
pTeam other = src.getTeam(id);
if (other)
other->changeId(src.Id);
t.changeId(id);
};
if (!merged) {
auto mcN = tmByClassName.find(make_pair(t.getClassId(false), t.getName()));
if (mcN != tmByClassName.end()) {
if (okMerge)
mergeItem(mcN->second, t);
merged = true;
updateIdT(mcN->second->Id);
}
}
if (!merged && okMerge) {
if (tm.count(t.Id)) {
int newId = max(getFreeTeamId(), src.getFreeTeamId());
t.changeId(newId);
}
pTeam pNew = addTeam(t, false);
updateNewItem(pNew, t);
}
srcTeam.insert(t.Id);
}
}
computeRemove(Teams, srcTeam, rTeam);
}
auto removeEnts = [&numRemove](const set<int> &ids, auto get) {
for (int id : ids) {
pBase b = get(id);
if (b && b->canRemove()) {
b->remove();
numRemove++;
}
}
};
removeEnts(rTeam, [this](int id) -> pBase {return getTeam(id); });
removeEnts(rRunner, [this](int id) -> pBase {return getRunner(id, 0); });
removeEnts(rClub, [this](int id) -> pBase {return getClub(id); });
removeEnts(rClass, [this](int id) -> pBase {return getClass(id); });
removeEnts(rCourse, [this](int id) -> pBase {return getCourse(id); });
removeEnts(rControl, [this](int id) -> pBase {return getControl(id); });
wstring mtOut(thisMergeTime.begin(), thisMergeTime.end());
addMergeInfo(mergeTag, mtOut);
synchronize();
}
wstring oEvent::cloneCompetition(bool cloneRunners, bool cloneTimes,
bool cloneCourses, bool cloneResult, bool addToDate) {
if (cloneResult) {
cloneTimes = true;
cloneCourses = true;
}
if (cloneTimes)
cloneRunners = true;
oEvent ce(gdibase);
ce.newCompetition(Name);
ce.ZeroTime = ZeroTime;
ce.Date = Date;
if (addToDate) {
SYSTEMTIME st;
convertDateYMS(Date, st, false);
__int64 absD = SystemTimeToInt64Second(st);
absD += 3600 * 24;
ce.Date = convertSystemDate(Int64SecondToSystemTime(absD));
}
int len = Name.length();
if (len > 2 && isdigit(Name[len - 1]) && !isdigit(Name[len - 2])) {
++ce.Name[len - 1]; // E1 -> E2
}
else
ce.Name += L" E2";
memcpy(ce.oData, oData, sizeof(oData));
for (oClubList::iterator it = Clubs.begin(); it != Clubs.end(); ++it) {
if (it->isRemoved())
continue;
pClub pc = ce.addClub(it->name, it->Id);
memcpy(pc->oData, it->oData, sizeof(pc->oData));
}
if (cloneCourses) {
for (oControlList::iterator it = Controls.begin(); it != Controls.end(); ++it) {
if (it->isRemoved())
continue;
pControl pc = ce.addControl(it->Id, 100, it->Name);
pc->setNumbers(it->codeNumbers());
pc->Status = it->Status;
memcpy(pc->oData, it->oData, sizeof(pc->oData));
}
for (oCourseList::iterator it = Courses.begin(); it != Courses.end(); ++it) {
if (it->isRemoved())
continue;
pCourse pc = ce.addCourse(it->Name, it->Length, it->Id);
pc->importControls(it->getControls(), false, false);
pc->legLengths = it->legLengths;
memcpy(pc->oData, it->oData, sizeof(pc->oData));
}
}
for (oClassList::iterator it = Classes.begin(); it != Classes.end(); ++it) {
if (it->isRemoved())
continue;
pClass pc = ce.addClass(it->Name, 0, it->Id);
memcpy(pc->oData, it->oData, sizeof(pc->oData));
pc->setNumStages(it->getNumStages());
pc->legInfo = it->legInfo;
if (cloneCourses) {
pc->Course = ce.getCourse(it->getCourseId());
pc->MultiCourse = it->MultiCourse; // Points to wrong competition, but valid for now...
}
}
if (cloneRunners) {
for (oRunnerList::iterator it = Runners.begin(); it != Runners.end(); ++it) {
if (it->isRemoved())
continue;
oRunner r(&ce, it->Id);
r.sName = it->sName;
r.getRealName(r.sName, r.tRealName);
r.StartNo = it->StartNo;
r.cardNumber = it->cardNumber;
r.Club = ce.getClub(it->getClubId());
r.Class = ce.getClass(it->getClassId(false));
if (cloneCourses)
r.Course = ce.getCourse(it->getCourseId());
pRunner pr = ce.addRunner(r, false);
pr->decodeMultiR(it->codeMultiR());
memcpy(pr->oData, it->oData, sizeof(pr->oData));
if (cloneTimes) {
pr->startTime = it->startTime;
}
if (cloneResult) {
if (it->Card) {
pr->Card = ce.addCard(*it->Card);
pr->Card->tOwner = pr;
}
pr->FinishTime = it->FinishTime;
pr->status = it->status;
}
}
for (oTeamList::iterator it = Teams.begin(); it != Teams.end(); ++it) {
if (it->skip())
continue;
oTeam t(&ce, it->Id);
t.sName = it->sName;
t.StartNo = it->StartNo;
t.Club = ce.getClub(it->getClubId());
t.Class = ce.getClass(it->getClassId(false));
if (cloneTimes)
t.startTime = it->startTime;
pTeam pt = ce.addTeam(t, false);
memcpy(pt->oData, it->oData, sizeof(pt->oData));
pt->Runners.resize(it->Runners.size());
for (size_t k = 0; k<it->Runners.size(); k++) {
int id = it->Runners[k] ? it->Runners[k]->Id : 0;
if (id)
pt->Runners[k] = ce.getRunner(id, 0);
}
t.apply(ChangeType::Update, nullptr);
}
for (oRunnerList::iterator it = ce.Runners.begin(); it != ce.Runners.end(); ++it) {
it->createMultiRunner(false, false);
}
}
vector<pRunner> changedClass, changedClassNoResult, assignedVacant, newEntries, notTransfered, noAssign;
set<int> dummy;
transferResult(ce, dummy, TransferAnyway, false, changedClass, changedClassNoResult, assignedVacant, newEntries, notTransfered, noAssign);
vector<pTeam> newEntriesT, notTransferedT, noAssignT;
transferResult(ce, TransferAnyway, newEntriesT, notTransferedT, noAssignT);
int eventNumberCurrent = getStageNumber();
if (eventNumberCurrent <= 0) {
eventNumberCurrent = 1;
setStageNumber(eventNumberCurrent);
}
ce.getDI().setString("PreEvent", currentNameId);
ce.setStageNumber(eventNumberCurrent + 1);
getDI().setString("PostEvent", ce.currentNameId);
int nf = getMeOSFeatures().getNumFeatures();
for (int k = 0; k < nf; k++) {
MeOSFeatures::Feature f = getMeOSFeatures().getFeature(k);
if (getMeOSFeatures().hasFeature(f))
ce.getMeOSFeatures().useFeature(f, true, ce);
}
// Transfer lists and list configurations.
if (listContainer) {
loadGeneralResults(false, false);
swap(ce.generalResults, generalResults);
try {
ce.listContainer = new MetaListContainer(&ce, *listContainer);
ce.save();
}
catch (...) {
swap(ce.generalResults, generalResults);
throw;
}
swap(ce.generalResults, generalResults);
}
return ce.CurrentFile;
}
void oEvent::transferListsAndSave(const oEvent &src) {
src.loadGeneralResults(false, false);
swap(src.generalResults, generalResults);
try {
src.getListContainer().synchronizeTo(getListContainer());
save();
}
catch (...) {
swap(src.generalResults, generalResults);
throw;
}
swap(src.generalResults, generalResults);
}
bool checkTargetClass(pRunner target, pRunner source,
const oClassList &Classes,
const vector<pRunner> &targetVacant,
vector<pRunner> &changedClass,
oEvent::ChangedClassMethod changeClassMethod) {
if (changeClassMethod == oEvent::TransferAnyway)
return true;
if (!compareClassName(target->getClass(false), source->getClass(false))) {
// Store all vacant positions in the right class
int targetClass = -1;
if (target->getStatus() == StatusOK) {
// There is already a result. Do not change class!
return false;
}
if (changeClassMethod == oEvent::TransferNoResult)
return false; // Do not allow change class, do not transfer result
for (oClassList::const_iterator cit = Classes.begin(); cit != Classes.end(); ++cit) {
if (cit->isRemoved())
continue;
if (compareClassName(cit->getName(), source->getClass(false))) {
targetClass = cit->getId();
if (targetClass == source->getClassId(false) || cit->getName() == source->getClass(false))
break; // Assume exact match
}
}
if (targetClass != -1) {
set<int> vacantIx;
for (size_t j = 0; j < targetVacant.size(); j++) {
if (!targetVacant[j])
continue;
if (targetVacant[j]->getClassId(false) == targetClass)
vacantIx.insert(j);
}
int posToUse = -1;
if (vacantIx.size() == 1)
posToUse = *vacantIx.begin();
else if (vacantIx.size() > 1) {
wstring srcBib = source->getBib();
if (srcBib.length() > 0) {
for (set<int>::iterator tit = vacantIx.begin(); tit != vacantIx.end(); ++tit) {
if (targetVacant[*tit]->getBib() == srcBib) {
posToUse = *tit;
break;
}
}
}
if (posToUse == -1)
posToUse = *vacantIx.begin();
}
if (posToUse != -1) {
// Change class or change class vacant
changedClass.push_back(target);
int oldStart = target->getStartTime();
wstring oldBib = target->getBib();
int oldSN = target->getStartNo();
int oldClass = target->getClassId(false);
pRunner tgt = targetVacant[posToUse];
target->cloneStartTime(tgt);
target->setBib(tgt->getBib(), 0, false);
target->setStartNo(tgt->getStartNo(), oBase::ChangeType::Update);
target->setClassId(tgt->getClassId(false), false);
tgt->setStartTime(oldStart, true, oBase::ChangeType::Update);
tgt->setBib(oldBib, 0, false);
tgt->setStartNo(oldSN, oBase::ChangeType::Update);
tgt->setClassId(oldClass, false);
return true; // Changed to correct class
}
else if (changeClassMethod == oEvent::ChangeClass) {
// Simpliy change class
target->setClassId(targetClass, false);
return true;
}
}
return false; // Wrong class, ChangeClass (but failed)
}
return true; // Same class, OK
}
void oEvent::transferResult(oEvent &ce,
const set<int> &allowNewEntries,
ChangedClassMethod changeClassMethod,
bool transferAllNoCompete,
vector<pRunner> &changedClass,
vector<pRunner> &changedClassNoResult,
vector<pRunner> &assignedVacant,
vector<pRunner> &newEntries,
vector<pRunner> &notTransfered,
vector<pRunner> &noAssignmentTarget) {
inthashmap processed(ce.Runners.size());
inthashmap used(Runners.size());
changedClass.clear();
changedClassNoResult.clear();
assignedVacant.clear();
newEntries.clear();
notTransfered.clear();
noAssignmentTarget.clear();
vector<pRunner> targetRunners;
vector<pRunner> targetVacant;
targetRunners.reserve(ce.Runners.size());
for (oRunnerList::iterator it = ce.Runners.begin(); it != ce.Runners.end(); ++it) {
if (!it->skip()) {
if (!it->isVacant())
targetRunners.push_back(&*it);
else
targetVacant.push_back(&*it);
}
}
calculateResults({}, ResultType::TotalResult);
// Lookup by id
for (size_t k = 0; k < targetRunners.size(); k++) {
pRunner it = targetRunners[k];
pRunner r = getRunner(it->Id, 0);
if (!r)
continue;
__int64 id1 = r->getExtIdentifier();
__int64 id2 = it->getExtIdentifier();
if (id1>0 && id2>0 && id1 != id2)
continue;
wstring cnA = canonizeName(it->sName.c_str());
wstring cnB = canonizeName(r->sName.c_str());
wstring ccnA = canonizeName(it->getClub().c_str());
wstring ccnB = canonizeName(r->getClub().c_str());
if ((id1>0 && id1 == id2) ||
(r->cardNumber>0 && r->cardNumber == it->cardNumber) ||
(it->sName == r->sName) || (cnA == cnB && ccnA == ccnB)) {
processed.insert(it->Id, 1);
used.insert(r->Id, 1);
if (checkTargetClass(it, r, ce.Classes, targetVacant, changedClass, changeClassMethod))
it->setInputData(*r);
else {
it->resetInputData();
changedClassNoResult.push_back(it);
}
}
}
if (processed.size() < int(targetRunners.size())) {
// Lookup by card
int v;
for (size_t k = 0; k < targetRunners.size(); k++) {
pRunner it = targetRunners[k];
if (processed.lookup(it->Id, v))
continue;
if (it->getCardNo() > 0) {
pRunner r = getRunnerByCardNo(it->getCardNo(), 0, CardLookupProperty::Any);
if (!r || used.lookup(r->Id, v))
continue;
__int64 id1 = r->getExtIdentifier();
__int64 id2 = it->getExtIdentifier();
if (id1>0 && id2>0 && id1 != id2)
continue;
if ((id1>0 && id1 == id2) || (it->sName == r->sName && it->getClub() == r->getClub())) {
processed.insert(it->Id, 1);
used.insert(r->Id, 1);
if (checkTargetClass(it, r, ce.Classes, targetVacant, changedClass, changeClassMethod))
it->setInputData(*r);
else {
it->resetInputData();
changedClassNoResult.push_back(it);
}
}
}
}
}
int v = -1;
// Store remaining runners
vector<pRunner> remainingRunners;
for (oRunnerList::iterator it2 = Runners.begin(); it2 != Runners.end(); ++it2) {
if (it2->skip() || used.lookup(it2->Id, v))
continue;
if (it2->isVacant())
continue; // Ignore vacancies on source side
remainingRunners.push_back(&*it2);
}
if (processed.size() < int(targetRunners.size()) && !remainingRunners.empty()) {
// Lookup by name / ext id
vector<int> cnd;
for (size_t k = 0; k < targetRunners.size(); k++) {
pRunner it = targetRunners[k];
if (processed.lookup(it->Id, v))
continue;
__int64 id1 = it->getExtIdentifier();
cnd.clear();
for (size_t j = 0; j < remainingRunners.size(); j++) {
pRunner src = remainingRunners[j];
if (!src)
continue;
if (id1 > 0) {
__int64 id2 = src->getExtIdentifier();
if (id2 == id1) {
cnd.clear();
cnd.push_back(j);
break; //This is the one, if they have the same Id there will be a unique match below
}
}
if (it->sName == src->sName && it->getClub() == src->getClub())
cnd.push_back(j);
}
if (cnd.size() == 1) {
pRunner &src = remainingRunners[cnd[0]];
processed.insert(it->Id, 1);
used.insert(src->Id, 1);
if (checkTargetClass(it, src, ce.Classes, targetVacant, changedClass, changeClassMethod)) {
it->setInputData(*src);
}
else {
it->resetInputData();
changedClassNoResult.push_back(it);
}
src = 0;
}
else if (cnd.size() > 0) { // More than one candidate
int winnerIx = -1;
int point = -1;
for (size_t j = 0; j < cnd.size(); j++) {
pRunner src = remainingRunners[cnd[j]];
int p = 0;
if (src->getClass(false) == it->getClass(false))
p += 1;
if (src->getBirthYear() == it->getBirthYear())
p += 2;
if (p > point) {
winnerIx = cnd[j];
point = p;
}
}
if (winnerIx != -1) {
processed.insert(it->Id, 1);
pRunner winner = remainingRunners[winnerIx];
remainingRunners[winnerIx] = 0;
used.insert(winner->Id, 1);
if (checkTargetClass(it, winner, ce.Classes, targetVacant, changedClass, changeClassMethod)) {
it->setInputData(*winner);
}
else {
it->resetInputData();
changedClassNoResult.push_back(it);
}
}
}
}
}
// Transfer vacancies
for (size_t k = 0; k < remainingRunners.size(); k++) {
pRunner src = remainingRunners[k];
if (!src || used.lookup(src->Id, v))
continue;
bool forceSkip = src->hasFlag(oAbstractRunner::FlagTransferSpecified) &&
!src->hasFlag(oAbstractRunner::FlagTransferNew);
if (forceSkip) {
notTransfered.push_back(src);
continue;
}
pRunner targetVacant = ce.getRunner(src->getId(), 0);
if (targetVacant && targetVacant->isVacant() && compareClassName(targetVacant->getClass(false), src->getClass(false))) {
targetVacant->setName(src->getName(), false);
targetVacant->setClub(src->getClub());
targetVacant->setCardNo(src->getCardNo(), false);
targetVacant->cloneData(src);
assignedVacant.push_back(targetVacant);
}
else {
pClass dstClass = ce.getClass(src->getClassId(false));
if (dstClass && compareClassName(dstClass->getName(), src->getClass(false))) {
if ((!src->hasFlag(oAbstractRunner::FlagTransferSpecified) && allowNewEntries.count(src->getClassId(false)))
|| src->hasFlag(oAbstractRunner::FlagTransferNew)) {
if (src->getClubId() > 0)
ce.getClubCreate(src->getClubId(), src->getClub());
pRunner dst = ce.addRunner(src->getName(), src->getClubId(), src->getClassId(false),
src->getCardNo(), src->getBirthYear(), true);
dst->cloneData(src);
dst->setInputData(*src);
newEntries.push_back(dst);
}
else if (transferAllNoCompete) {
if (src->getClubId() > 0)
ce.getClubCreate(src->getClubId(), src->getClub());
pRunner dst = ce.addRunner(src->getName(), src->getClubId(), src->getClassId(false),
0, src->getBirthYear(), true);
dst->cloneData(src);
dst->setInputData(*src);
dst->setStatus(StatusNotCompetiting, true, ChangeType::Update);
notTransfered.push_back(dst);
}
else
notTransfered.push_back(src);
}
}
}
// Runners on target side not assigned a result
for (size_t k = 0; k < targetRunners.size(); k++) {
if (targetRunners[k] && !processed.count(targetRunners[k]->Id)) {
noAssignmentTarget.push_back(targetRunners[k]);
if (targetRunners[k]->inputStatus == StatusUnknown ||
(targetRunners[k]->inputStatus == StatusOK && targetRunners[k]->inputTime == 0)) {
targetRunners[k]->inputStatus = StatusNotCompetiting;
}
}
}
}
void oEvent::transferResult(oEvent &ce,
ChangedClassMethod changeClassMethod,
vector<pTeam> &newEntries,
vector<pTeam> &notTransfered,
vector<pTeam> &noAssignmentTarget) {
inthashmap processed(ce.Teams.size());
inthashmap used(Teams.size());
newEntries.clear();
notTransfered.clear();
noAssignmentTarget.clear();
vector<pTeam> targetTeams;
targetTeams.reserve(ce.Teams.size());
for (oTeamList::iterator it = ce.Teams.begin(); it != ce.Teams.end(); ++it) {
if (!it->skip()) {
targetTeams.push_back(&*it);
}
}
calculateTeamResults(set<int>(), ResultType::TotalResult);
// Lookup by id
for (size_t k = 0; k < targetTeams.size(); k++) {
pTeam it = targetTeams[k];
pTeam t = getTeam(it->Id);
if (!t)
continue;
__int64 id1 = t->getExtIdentifier();
__int64 id2 = it->getExtIdentifier();
if (id1>0 && id2>0 && id1 != id2)
continue;
if ((id1>0 && id1 == id2) || (it->sName == t->sName && it->getClub() == t->getClub())) {
processed.insert(it->Id, 1);
used.insert(t->Id, 1);
it->setInputData(*t);
//checkTargetClass(it, r, ce.Classes, targetVacant, changedClass);
}
}
int v = -1;
// Store remaining runners
vector<pTeam> remainingTeams;
for (oTeamList::iterator it2 = Teams.begin(); it2 != Teams.end(); ++it2) {
if (it2->skip() || used.lookup(it2->Id, v))
continue;
if (it2->isVacant())
continue; // Ignore vacancies on source side
remainingTeams.push_back(&*it2);
}
if (processed.size() < int(targetTeams.size()) && !remainingTeams.empty()) {
// Lookup by name / ext id
vector<int> cnd;
for (size_t k = 0; k < targetTeams.size(); k++) {
pTeam it = targetTeams[k];
if (processed.lookup(it->Id, v))
continue;
__int64 id1 = it->getExtIdentifier();
cnd.clear();
for (size_t j = 0; j < remainingTeams.size(); j++) {
pTeam src = remainingTeams[j];
if (!src)
continue;
if (id1 > 0) {
__int64 id2 = src->getExtIdentifier();
if (id2 == id1) {
cnd.clear();
cnd.push_back(j);
break; //This is the one, if they have the same Id there will be a unique match below
}
}
if (it->sName == src->sName && it->getClub() == src->getClub())
cnd.push_back(j);
}
if (cnd.size() == 1) {
pTeam &src = remainingTeams[cnd[0]];
processed.insert(it->Id, 1);
used.insert(src->Id, 1);
it->setInputData(*src);
//checkTargetClass(it, src, ce.Classes, targetVacant, changedClass);
src = 0;
}
else if (cnd.size() > 0) { // More than one candidate
int winnerIx = -1;
int point = -1;
for (size_t j = 0; j < cnd.size(); j++) {
pTeam src = remainingTeams[cnd[j]];
int p = 0;
if (src->getClass(false) == it->getClass(false))
p += 1;
if (p > point) {
winnerIx = cnd[j];
point = p;
}
}
if (winnerIx != -1) {
processed.insert(it->Id, 1);
pTeam winner = remainingTeams[winnerIx];
remainingTeams[winnerIx] = 0;
used.insert(winner->Id, 1);
it->setInputData(*winner);
//checkTargetClass(it, winner, ce.Classes, targetVacant, changedClass);
}
}
}
}
/*
// Transfer vacancies
for (size_t k = 0; k < remainingRunners.size(); k++) {
pRunner src = remainingRunners[k];
if (!src || used.lookup(src->Id, v))
continue;
pRunner targetVacant = ce.getRunner(src->getId(), 0);
if (targetVacant && targetVacant->isVacant() && compareClassName(targetVacant->getClass(), src->getClass()) ) {
targetVacant->setName(src->getName());
targetVacant->setClub(src->getClub());
targetVacant->setCardNo(src->getCardNo(), false);
targetVacant->cloneData(src);
assignedVacant.push_back(targetVacant);
}
else {
pClass dstClass = ce.getClass(src->getClassId());
if (dstClass && compareClassName(dstClass->getName(), src->getClass())) {
if (allowNewEntries.count(src->getClassId())) {
if (src->getClubId() > 0)
ce.getClubCreate(src->getClubId(), src->getClub());
pRunner dst = ce.addRunner(src->getName(), src->getClubId(), src->getClassId(),
src->getCardNo(), src->getBirthYear(), true);
dst->cloneData(src);
dst->setInputData(*src);
newEntries.push_back(dst);
}
else if (transferAllNoCompete) {
if (src->getClubId() > 0)
ce.getClubCreate(src->getClubId(), src->getClub());
pRunner dst = ce.addRunner(src->getName(), src->getClubId(), src->getClassId(),
0, src->getBirthYear(), true);
dst->cloneData(src);
dst->setInputData(*src);
dst->setStatus(StatusNotCompetiting);
notTransfered.push_back(dst);
}
else
notTransfered.push_back(src);
}
}
}
// Runners on target side not assigned a result
for (size_t k = 0; k < targetRunners.size(); k++) {
if (targetRunners[k] && !processed.count(targetRunners[k]->Id)) {
noAssignmentTarget.push_back(targetRunners[k]);
if (targetRunners[k]->inputStatus == StatusUnknown ||
(targetRunners[k]->inputStatus == StatusOK && targetRunners[k]->inputTime == 0)) {
targetRunners[k]->inputStatus = StatusNotCompetiting;
}
}
}*/
}
void oAbstractRunner::merge(const oBase &input) {
const oAbstractRunner &src = dynamic_cast<const oAbstractRunner&>(input);
setName(src.sName, false);
setStartTime(src.startTime, true, ChangeType::Update, false);
setFinishTime(src.FinishTime);
setStatus(src.status, true, ChangeType::Update);
setStartNo(src.StartNo, ChangeType::Update);
setClubId(src.getClubId());
setClassId(src.getClassId(false), false);
setInputPlace(src.inputPlace);
if (inputTime != src.inputTime) {
inputTime = src.inputTime;
updateChanged();
}
setInputStatus(src.inputStatus);
setInputPoints(src.inputPoints);
}
void oRunner::merge(const oBase &input) {
oAbstractRunner::merge(input);
const oRunner &src = dynamic_cast<const oRunner&>(input);
setCourseId(src.getCourseId());
if (src.getCardId() != 0)
setCard(src.getCardId());
setCardNo(src.getCardNo(), false);
if (memcmp(oData, src.oData, sizeof(oData)) != 0) {
memcpy(oData, src.oData, sizeof(oData));
updateChanged();
}
synchronize(true);
}
void oTeam::merge(const oBase &input) {
oAbstractRunner::merge(input);
const oTeam &src = dynamic_cast<const oTeam&>(input);
bool same = src.Runners.size() == Runners.size();
vector<int> r(src.Runners.size());
for (size_t i = 0; i < src.Runners.size(); i++) {
if (src.Runners[i]) {
r[i] = src.Runners[i]->Id;
src.Runners[i]->tInTeam = nullptr;
}
if (same) {
int rc = Runners[i] ? Runners[i]->Id : 0;
if (rc != r[i])
same = false;
}
}
importRunners(r);
if (!same)
updateChanged();
if (memcmp(oData, src.oData, sizeof(oData)) != 0) {
memcpy(oData, src.oData, sizeof(oData));
updateChanged();
}
synchronize(true);
}
void oControl::merge(const oBase &input) {
const oControl &src = dynamic_cast<const oControl&>(input);
if (src.Name.length() > 0)
setName(src.Name);
setNumbers(src.codeNumbers());
setStatus(src.getStatus());
if (memcmp(oData, src.oData, sizeof(oData)) != 0) {
memcpy(oData, src.oData, sizeof(oData));
updateChanged();
}
synchronize(true);
}
void oCourse::merge(const oBase &input) {
const oCourse &src = dynamic_cast<const oCourse&>(input);
if (src.Name.length() > 0)
setName(src.Name);
setLength(src.Length);
importControls(src.getControls(), true, false);
importLegLengths(src.getLegLengths(), true);
if (memcmp(oData, src.oData, sizeof(oData)) != 0) {
memcpy(oData, src.oData, sizeof(oData));
updateChanged();
}
synchronize(true);
}
void oClass::merge(const oBase &input) {
const oClass &src = dynamic_cast<const oClass&>(input);
if (src.Name.length() > 0)
setName(src.Name, true);
setCourse(oe->getCourse(src.getCourseId()));
if (src.MultiCourse.size() > 0) {
vector<vector<pCourse>> mcCopy = MultiCourse;
set<int> cid;
vector< vector<int> > multi;
parseCourses(src.codeMultiCourse(), multi, cid);
importCourses(multi);
if (mcCopy != MultiCourse)
updateChanged();
}
else {
setNumStages(0);
}
if (src.legInfo.size() > 0) {
if (codeLegMethod() != src.codeLegMethod()) {
importLegMethod(src.codeLegMethod());
updateChanged();
}
}
if (memcmp(oData, src.oData, sizeof(oData)) != 0) {
memcpy(oData, src.oData, sizeof(oData));
updateChanged();
}
synchronize(true);
}
void oClub::merge(const oBase &input) {
const oClub &src = dynamic_cast<const oClub&>(input);
setName(src.getName());
if (memcmp(oData, src.oData, sizeof(oData)) != 0) {
memcpy(oData, src.oData, sizeof(oData));
updateChanged();
}
synchronize(true);
}
void oCard::merge(const oBase &input) {
const oCard &src = dynamic_cast<const oCard&>(input);
setCardNo(src.getCardNo());
if (readId != src.readId) {
readId = src.readId;
updateChanged();
}
if (getPunchString() != src.getPunchString()) {
importPunches(src.getPunchString());
updateChanged();
}
synchronize(true);
}
void oPunch::merge(const oBase &input) {
const oPunch &src = dynamic_cast<const oPunch&>(input);
// Not implemented
}
void oFreePunch::merge(const oBase &input) {
const oFreePunch &src = dynamic_cast<const oFreePunch&>(input);
// Not implemented
}
void oEvent::merge(const oBase &srcIn) {
}
wstring oEvent::getMergeTag(bool forceReset) {
wstring sm = getDCI().getString("MergeTag");
if (sm.empty() || forceReset) {
static const char alphanum[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
random_device r;
mt19937 e1(r());
wchar_t s[13];
for (int i = 0; i < 12; ++i) {
s[i] = alphanum[rand() % (sizeof(alphanum) - 1)];
}
s[12] = 0;
getDI().setString("MergeTag", s);
synchronize(true);
sm = s;
}
return sm;
}
wstring oEvent::getMergeInfo(const wstring &tag) const {
wstring mv = getDCI().getString("MergeInfo");
vector<wstring> mvv;
split(mv, L":", mvv);
for (size_t j = 0; j + 1 < mvv.size(); j += 2) {
if (mvv[j] == tag)
return mvv[j + 1];
}
return L"";
}
void oEvent::addMergeInfo(const wstring &tag, const wstring &version) {
wstring mv = getDCI().getString("MergeInfo");
vector<wstring> mvv;
split(mv, L":", mvv);
bool ok = false;
for (size_t j = 0; j + 1 < mvv.size(); j += 2) {
if (mvv[j] == tag)
mvv[j + 1] = version, ok = true;
}
if (!ok) {
mvv.push_back(tag);
mvv.push_back(version);
}
unsplit(mvv, L":", mv);
getDI().setString("MergeInfo", mv);
}
string oEvent::getLastModified() const {
string s;
auto maxModified = [&s](auto &cnt) {
for (auto &b : cnt)
if (!b.isRemoved() && b.getStamp() > s)
s = b.getStamp();
};
maxModified(Controls);
maxModified(Courses);
maxModified(Classes);
maxModified(Cards);
maxModified(Clubs);
maxModified(Runners);
maxModified(Teams);
// maxModified(punches); xxx ignored
return s;
}