/************************************************************************
    MeOS - Orienteering Software
    Copyright (C) 2009-2017 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 <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 <algorithm>
#include <cassert>

#include "iof30interface.h"
#include "oEvent.h"
#include "gdioutput.h"
#include "gdifonts.h"
#include "xmlparser.h"
#include "RunnerDB.h"
#include "meos_util.h"
#include "meosException.h"
#include "localizer.h"

wstring &getFirst(wstring &inout, int maxNames);
wstring getMeosCompectVersion();

IOF30Interface::IOF30Interface(oEvent *oe, bool forceSplitFee) : oe(*oe), useGMT(false), teamsAsIndividual(false), 
                                entrySourceId(1), unrollLoops(true), 
                                includeStageRaceInfo(true) {
  cachedStageNumber = -1;
  splitLateFee = forceSplitFee || oe->getPropertyInt("SplitLateFees", false) == 1;
}

void IOF30Interface::readCourseData(gdioutput &gdi, const xmlobject &xo, bool updateClass,
                                    int &courseCount, int &failed) {
  string ver;
  xo.getObjectString("iofVersion", ver);
  if (!ver.empty() && ver > "3.0")
    gdi.addString("", 0, "Varning, okänd XML-version X#" + ver);
  courseCount = 0;
  failed = 0;
  xmlList xl;
  xo.getObjects("RaceCourseData", xl);
  xmlList::const_iterator it;
  xmlobject xRaceCourses;
  if (xl.size() == 1) {
    xRaceCourses = xl[0];
  }
  else {
    int nr = getStageNumber();
    int ix = -1;
    for (size_t k = 0; k < xl.size(); k++) {
      if (xl[k].getObjectInt("raceNumber") == nr) {
        ix = k;
        break;
      }
    }
    if (ix == -1)
      throw meosException("Filen innehåller flera uppsättningar banor, men ingen har samma etappnummer som denna etapp (X).#" + itos(nr));
    else
      xRaceCourses = xl[ix];
  }

  xmlList xControls, xCourse, x;
  xRaceCourses.getObjects("Control", xControls);
  xRaceCourses.getObjects("Course", xCourse);

  for (size_t k = 0; k < xControls.size(); k++) {
    readControl(xControls[k]);
  }

  map<wstring, pCourse> courses;
  map<wstring, vector<pCourse> > coursesFamilies;

  for (size_t k = 0; k < xCourse.size(); k++) {
    pCourse pc = readCourse(xCourse[k]);
    if (pc) {
      courseCount++;
      if (courses.count(pc->getName()))
        gdi.addString("", 0, L"Varning: Banan 'X' förekommer flera gånger#" + pc->getName());

      courses[pc->getName()] = pc;

      wstring family;
      xCourse[k].getObjectString("CourseFamily", family);

      if (!family.empty()) {
        coursesFamilies[family].push_back(pc);
      }
    }
    else
      failed++;
  }

  if (!updateClass)
    return;


  xmlList xClassAssignment, xTeamAssignment, xPersonAssignment;
  xRaceCourses.getObjects("ClassCourseAssignment", xClassAssignment);
  if (xClassAssignment.size() > 0)
    classCourseAssignment(gdi, xClassAssignment, courses, coursesFamilies);

  xRaceCourses.getObjects("PersonCourseAssignment", xPersonAssignment);
  if (xPersonAssignment.size() > 0)
    personCourseAssignment(gdi, xPersonAssignment, courses);

  xRaceCourses.getObjects("TeamCourseAssignment", xTeamAssignment);
  if (xTeamAssignment.size() > 0)
    teamCourseAssignment(gdi, xTeamAssignment, courses);

  xmlList xAssignment;
  xRaceCourses.getObjects("CourseAssignment", xAssignment);
  if (xAssignment.size() > 0) {
    classAssignmentObsolete(gdi, xAssignment, courses, coursesFamilies);
  }

}

void IOF30Interface::classCourseAssignment(gdioutput &gdi, xmlList &xAssignment,
                                           const map<wstring, pCourse> &courses,
                                           const map<wstring, vector<pCourse> > &coursesFamilies) {

  map< pair<int, int>, vector<wstring> > classIdLegToCourse;

  for (size_t k = 0; k < xAssignment.size(); k++) {
    xmlobject &xClsAssignment = xAssignment[k];
    map<int, vector<int> > cls2Stages;

    xmlList xClsId;
    xClsAssignment.getObjects("ClassId", xClsId);
    for (size_t j = 0; j <xClsId.size(); j++) {
      int id = xClsId[j].getInt();
      if (oe.getClass(id) == 0) {
        gdi.addString("", 0, "Klass saknad").setColor(colorRed);
      }
      else
        cls2Stages.insert(make_pair(id, vector<int>()));
    }

    if (cls2Stages.empty()) {
      wstring cname;
      xClsAssignment.getObjectString("ClassName", cname);
      if (cname.length() > 0) {
        pClass pc = oe.getClassCreate(0, cname);
        if (pc)
          cls2Stages.insert(make_pair(pc->getId(), vector<int>()) );
      }
    }

    if (cls2Stages.empty()) {
      gdi.addString("", 0, "Klass saknad").setColor(colorRed);
      continue;
    }

    // Allowed on leg
    xmlList xLeg;
    xClsAssignment.getObjects("AllowedOnLeg", xLeg);

    for (map<int, vector<int> >::iterator it = cls2Stages.begin(); it != cls2Stages.end(); ++it) {
      pClass defClass = oe.getClass(it->first);
      vector<int> &legs = it->second;

      // Convert from leg/legorder to real leg number
      for (size_t j = 0; j <xLeg.size(); j++) {
        int leg = xLeg[j].getInt()-1;
        if (defClass && defClass->getNumStages() > 0) {
          for (unsigned i = 0; i < defClass->getNumStages(); i++) {
            int realLeg, legIx;
            defClass->splitLegNumberParallel(i, realLeg, legIx);
            if (realLeg == leg)
              legs.push_back(i);
          }
        }
        else
          legs.push_back(leg);
      }
      if (legs.empty())
        legs.push_back(-1); // All legs
    }
    // Extract courses / families
    xmlList xCourse;
    xClsAssignment.getObjects("CourseName", xCourse);

    xmlList xFamily;
    wstring t, t1, t2;
    xClsAssignment.getObjects("CourseFamily", xFamily);

    for (map<int, vector<int> >::iterator it = cls2Stages.begin(); it != cls2Stages.end(); ++it) {
      const vector<int> &legs = it->second;
      for (size_t m = 0; m < legs.size(); m++) {
        int leg = legs[m];
        for (size_t j = 0; j < xFamily.size(); j++) {
          for (size_t i = 0; i < xCourse.size(); i++) {
            wstring crs = constructCourseName(xFamily[j].getObjectString(0, t1),
                                              xCourse[i].getObjectString(0, t2));
            classIdLegToCourse[make_pair(it->first, leg)].push_back(crs);
          }
        }
        if (xFamily.empty()) {
          for (size_t i = 0; i < xCourse.size(); i++) {
            wstring crs = constructCourseName(L"", xCourse[i].getObjectString(0, t));
            classIdLegToCourse[make_pair(it->first, leg)].push_back(crs);
          }
        }
        if (xCourse.empty()) {
          for (size_t j = 0; j < xFamily.size(); j++) {
            map<wstring, vector<pCourse> >::const_iterator res  =
                         coursesFamilies.find(xFamily[j].getObjectString(0, t));


            if (res != coursesFamilies.end()) {
              const vector<pCourse> &family = res->second;
              for (size_t i = 0; i < family.size(); i++) {
                classIdLegToCourse[make_pair(it->first, leg)].push_back(family[i]->getName());
              }
            }
          }
        }
      }
    }
  }

  map< pair<int, int>, vector<wstring> >::iterator it;
  for (it = classIdLegToCourse.begin(); it != classIdLegToCourse.end(); ++it) {
    pClass pc = oe.getClass(it->first.first);
    if (pc) {
      pc->setCourse(0);
      for (size_t k = 0; k < pc->getNumStages(); k++)
        pc->clearStageCourses(k);
    }
  }
  for (it = classIdLegToCourse.begin(); it != classIdLegToCourse.end(); ++it) {
    pClass pc = oe.getClass(it->first.first);
    unsigned leg = it->first.second;
    const vector<wstring> &crs = it->second;
    vector<pCourse> pCrs;
    for (size_t k = 0; k < crs.size(); k++) {
      map<wstring, pCourse>::const_iterator res = courses.find(crs[k]);
      pCourse c = res != courses.end() ? res->second : 0;
      if (c == 0)
        gdi.addString("", 0, L"Varning: Banan 'X' finns inte#" + crs[k]).setColor(colorRed);
      pCrs.push_back(c);
    }
    if (pCrs.empty())
      continue;

    if (leg == -1) {
      if (pCrs.size() > 1) {
        if (!pc->hasMultiCourse()) {
          pc->setNumStages(1);
        }
      }

      if (pc->hasMultiCourse()) {
        for (size_t k = 0; k < pc->getNumStages(); k++) {
          for (size_t j = 0; j < pCrs.size(); j++)
            pc->addStageCourse(k, pCrs[j]);
        }
      }
      else
        pc->setCourse(pCrs[0]);
    }
    else if (leg == 0 && pCrs.size() == 1) {
      if (pc->hasMultiCourse())
        pc->addStageCourse(0, pCrs[0]);
      else
        pc->setCourse(pCrs[0]);
    }
    else {
      if (leg >= pc->getNumStages())
        pc->setNumStages(leg+1);

      for (size_t j = 0; j < pCrs.size(); j++)
        pc->addStageCourse(leg, pCrs[j]);
    }
  }
}

void IOF30Interface::personCourseAssignment(gdioutput &gdi, xmlList &xAssignment,
                                            const map<wstring, pCourse> &courses) {
  vector<pRunner> allR;
  oe.getRunners(0, 0, allR, false);
  map<wstring, pRunner> bib2Runner;
  multimap<wstring, pRunner> name2Runner;
  for (size_t k = 0; k < allR.size(); k++) {
    wstring bib = allR[k]->getBib();
    if (!bib.empty())
      bib2Runner[bib] = allR[k];

    name2Runner.insert(make_pair(allR[k]->getName(), allR[k]));
  }

  for (size_t k = 0; k < xAssignment.size(); k++) {
    xmlobject &xPAssignment = xAssignment[k];
    pRunner r = 0;

    wstring runnerText;
    wstring bib;
    xPAssignment.getObjectString("BibNumber", bib);

    if (!bib.empty()) {
      runnerText = bib;
      r = bib2Runner[bib];
    }

    if (r == 0) {
      int id = xPAssignment.getObjectInt("EntryId"); // This assumes entryId = personId, which may or may not be the case.
      if (id != 0) {
        runnerText = L"Id = " + itow(id);
        r = oe.getRunner(id, 0);
      }
    }

    if (r == 0) {
      wstring person;
      xPAssignment.getObjectString("PersonName", person);
      if (!person.empty()) {
        runnerText = person;
        wstring cls;
        xPAssignment.getObjectString("ClassName", cls);
        multimap<wstring, pRunner>::const_iterator res = name2Runner.find(person);
        while (res != name2Runner.end() && person == res->first) {
          if (cls.empty() || res->second->getClass(false) == cls) {
            r = res->second;
            break;
          }
          ++res;
        }
      }
    }

    if (r == 0) {
      gdi.addString("", 0, L"Varning: Deltagaren 'X' finns inte.#" + runnerText).setColor(colorRed);
      continue;
    }

    pCourse c = findCourse(gdi, courses, xPAssignment);
    if (c == 0)
      continue;

    r->setCourseId(c->getId());
  }
}

pCourse IOF30Interface::findCourse(gdioutput &gdi,
                                   const map<wstring, pCourse> &courses,
                                   xmlobject &xPAssignment) {
  wstring course;
  xPAssignment.getObjectString("CourseName", course);
  wstring family;
  xPAssignment.getObjectString("CourseFamily", family);
  wstring fullCrs = constructCourseName(family, course);

  map<wstring, pCourse>::const_iterator res = courses.find(fullCrs);
  pCourse c = res != courses.end() ? res->second : 0;
  if (c == 0) {
    gdi.addString("", 0, L"Varning: Banan 'X' finns inte.#" + fullCrs).setColor(colorRed);
  }
  return c;
}

void IOF30Interface::teamCourseAssignment(gdioutput &gdi, xmlList &xAssignment,
                                            const map<wstring, pCourse> &courses) {
  vector<pTeam> allT;
  oe.getTeams(0, allT, false);

  map<wstring, pTeam> bib2Team;
  map<pair<wstring, wstring>, pTeam> nameClass2Team;
  for (size_t k = 0; k < allT.size(); k++) {
    wstring bib = allT[k]->getBib();
    if (!bib.empty())
      bib2Team[bib] = allT[k];

    nameClass2Team[make_pair(allT[k]->getName(), allT[k]->getClass(false))] = allT[k];
  }

  for (size_t k = 0; k < xAssignment.size(); k++) {
    xmlobject &xTAssignment = xAssignment[k];
    pTeam t = 0;
    wstring teamText;
    wstring bib;
    xTAssignment.getObjectString("BibNumber", bib);

    if (!bib.empty()) {
      teamText = bib;
      t = bib2Team[bib];
    }

    if (t == 0) {
      wstring team;
      xTAssignment.getObjectString("TeamName", team);
      if (!team.empty()) {
        wstring cls;
        xTAssignment.getObjectString("ClassName", cls);
        t = nameClass2Team[make_pair(team, cls)];
        teamText = team + L" / " + cls;
      }
    }

    if (t == 0) {
      gdi.addString("", 0, L"Varning: Laget 'X' finns inte.#" + teamText).setColor(colorRed);
      continue;
    }

    xmlList teamMemberAssignment;
    xTAssignment.getObjects("TeamMemberCourseAssignment", teamMemberAssignment);
    assignTeamCourse(gdi, *t, teamMemberAssignment, courses);
  }
}

void IOF30Interface::assignTeamCourse(gdioutput &gdi, oTeam &team, xmlList &xAssignment,
                                      const map<wstring, pCourse> &courses) {

  if (!team.getClassRef(false))
    return;
  for (size_t k = 0; k <xAssignment.size(); k++) {

    // Extract courses / families
    pCourse c = findCourse(gdi, courses, xAssignment[k]);
    if (c == 0)
      continue;

    xmlobject xLeg = xAssignment[k].getObject("Leg");
    if (xLeg) {
      int leg = xLeg.getInt() - 1;
      int legorder = 0;
      xmlobject xLegOrder = xAssignment[k].getObject("LegOrder");
      if (xLegOrder)
        legorder = xLegOrder.getInt() - 1;

      int legId = team.getClassRef(false)->getLegNumberLinear(leg, legorder);
      if (legId>=0) {
        pRunner r = team.getRunner(legId);
        if (r == 0) {
          r = oe.addRunner(lang.tl(L"N.N."), team.getClubId(), team.getClassId(false), 0, 0, false);
          if (r) {
            r->setEntrySource(entrySourceId);
            r->flagEntryTouched(true);
          }
          team.setRunner(legId, r, false);
          r = team.getRunner(legId);
        }
        if (r) {
          r->setCourseId(c->getId());
        }
      }
      else
        gdi.addString("", 0, L"Bantilldelning för 'X' hänvisar till en sträcka som inte finns#" + team.getClass(false)).setColor(colorRed);
    }
    else {
      wstring name;
      xAssignment[k].getObjectString("TeamMemberName", name);
      if (!name.empty()) {
        for (int j = 0; j < team.getNumRunners(); j++) {
          pRunner r = team.getRunner(j);
          if (r && r->getName() == name) {
            r->setCourseId(c->getId());
            break;
          }
        }
      }
    }
  }
}


void IOF30Interface::classAssignmentObsolete(gdioutput &gdi, xmlList &xAssignment,
                                             const map<wstring, pCourse> &courses,
                                             const map<wstring, vector<pCourse> > &coursesFamilies) {
  map<int, vector<pCourse> > class2Courses;
  map<int, set<wstring> > class2Families;

  multimap<wstring, pRunner> bib2Runners;
  typedef multimap<wstring, pRunner>::iterator bibIterT;
  bool b2RInit = false;

  map<pair<wstring, wstring>, pTeam> clsName2Team;
  typedef map<pair<wstring, wstring>, pTeam>::iterator teamIterT;
  bool c2TeamInit = false;

  for (size_t k = 0; k < xAssignment.size(); k++) {
    wstring name = constructCourseName(xAssignment[k]);
    wstring family;
    xAssignment[k].getObjectString("CourseFamily", family);

    if ( courses.find(name) == courses.end() )
      gdi.addString("", 0, L"Varning: Banan 'X' finns inte#" + name);
    else {
      pCourse pc = courses.find(name)->second;
      xmlList xCls, xPrs;
      xAssignment[k].getObjects("Class", xCls);
      xAssignment[k].getObjects("Person", xPrs);

      for (size_t j = 0; j < xCls.size(); j++) {
        wstring cName;
        xCls[j].getObjectString("Name", cName);
        int id = xCls[j].getObjectInt("Id");
        pClass cls = oe.getClassCreate(id, cName);
        if (cls) {
          class2Courses[cls->getId()].push_back(pc);

          if (!family.empty()) {
            class2Families[cls->getId()].insert(family);
          }
        }
      }

      for (size_t j = 0; j < xPrs.size(); j++) {
        wstring bib;
        int leg = xPrs[j].getObjectInt("Leg");
        int legOrder = xPrs[j].getObjectInt("LegOrder");

        xPrs[j].getObjectString("BibNumber", bib);
        if (!bib.empty()) {
          if (!b2RInit) {
            // Setup bib2runner map
            vector<pRunner> r;
            oe.getRunners(0, 0, r);
            for (size_t i = 0; i < r.size(); i++) {
              wstring b = r[i]->getBib();
              if (!b.empty())
                bib2Runners.insert(make_pair(b, r[i]));
            }
            b2RInit = true;
          }

          pair<bibIterT, bibIterT> range = bib2Runners.equal_range(bib);
          for (bibIterT it = range.first; it != range.second; ++it) {
            int ln = it->second->getLegNumber();
            int rLegNumber = 0, rLegOrder = 0;
            if (it->second->getClassRef(false))
              it->second->getClassRef(false)->splitLegNumberParallel(ln, rLegNumber, rLegOrder);
            bool match = true;
            if (leg != 0 && leg != rLegNumber+1)
              match = false;
            if (legOrder != 0 && legOrder != rLegOrder+1)
              match = false;

            if (match) {
              it->second->setCourseId(pc->getId());
              it->second->synchronize();
            }
          }
          continue;
        }

        wstring className;
        wstring teamName;
        xPrs[j].getObjectString("ClassName", className);
        xPrs[j].getObjectString("TeamName", teamName);

        if (!teamName.empty()) {
          if (!c2TeamInit) {
            vector<pTeam> t;
            oe.getTeams(0, t);
            for (size_t i = 0; i < t.size(); i++)
              clsName2Team[make_pair(t[i]->getClass(false), t[i]->getName())] = t[i];
            c2TeamInit = true;
          }

          teamIterT res = clsName2Team.find(make_pair(className, teamName));

          if (res != clsName2Team.end()) {
            pClass cls = res->second->getClassRef(false);
            if (cls) {
              int ln = cls->getLegNumberLinear(leg, legOrder);
              pRunner r = res->second->getRunner(ln);
              if (r) {
                r->setCourseId(pc->getId());
                r->synchronize();
              }
            }
          }
          continue;
        }

        // Note: entryId is assumed to be equal to personId,
        // which is the only we have. This might not be true.
        int entryId = xPrs[j].getObjectInt("EntryId");
        pRunner r = oe.getRunner(entryId, 0);
        if (r) {
          r->setCourseId(pc->getId());
          r->synchronize();
        }
      }
    }
  }

  if (!class2Families.empty()) {
    vector<pClass> c;
    oe.getClasses(c, false);
    for (size_t k = 0; k < c.size(); k++) {
      bool assigned = false;

      if (class2Families.count(c[k]->getId())) {
        const set<wstring> &families = class2Families[c[k]->getId()];

        if (families.size() == 1) {
          int nl = c[k]->getNumStages();
          const vector<pCourse> &crsFam = coursesFamilies.find(*families.begin())->second;
          if (nl == 0) {
            if (crsFam.size() == 1)
              c[k]->setCourse(crsFam[0]);
            else {
              c[k]->setNumStages(1);
              c[k]->clearStageCourses(0);
              for (size_t j = 0; j < crsFam.size(); j++)
                c[k]->addStageCourse(0, crsFam[j]->getId());
            }
          }
          else {
            int nFam = crsFam.size();
            for (int i = 0; i < nl; i++) {
              c[k]->clearStageCourses(i);
              for (int j = 0; j < nFam; j++)
                c[k]->addStageCourse(i, crsFam[(j + i)%nFam]->getId());
            }
          }
          assigned = true;
        }
        else if (families.size() > 1) {
          int nl = c[k]->getNumStages();
          if (nl == 0) {
            c[k]->setNumStages(families.size());
            nl = families.size();
          }

          set<wstring>::const_iterator fit = families.begin();
          for (int i = 0; i < nl; i++, ++fit) {
            if (fit == families.end())
              fit = families.begin();
            c[k]->clearStageCourses(i);
            const vector<pCourse> &crsFam = coursesFamilies.find(*fit)->second;
            int nFam = crsFam.size();
            for (int j = 0; j < nFam; j++)
              c[k]->addStageCourse(i, crsFam[j]->getId());
          }

          assigned = true;
        }
      }

      if (!assigned && class2Courses.count(c[k]->getId())) {
        const vector<pCourse> &crs = class2Courses[c[k]->getId()];
        int nl = c[k]->getNumStages();

        if (crs.size() == 1 && nl == 0) {
          c[k]->setCourse(crs[0]);
        }
        else if (crs.size() > 1) {
          int nCrs = crs.size();
          for (int i = 0; i < nl; i++) {
            c[k]->clearStageCourses(i);
            for (int j = 0; j < nCrs; j++)
              c[k]->addStageCourse(i, crs[(j + i)%nCrs]->getId());
          }
        }
      }
      c[k]->synchronize();
    }
  }
}

void IOF30Interface::readCompetitorList(gdioutput &gdi, const xmlobject &xo, int &personCount) {
  if (!xo)
    return;

  string ver;
  xo.getObjectString("iofVersion", ver);
  if (!ver.empty() && ver > "3.0")
    gdi.addString("", 0, "Varning, okänd XML-version X#" + ver);

  xmlList xl;
  xo.getObjects(xl);

  xmlList::const_iterator it;

  for (it=xl.begin(); it != xl.end(); ++it) {
    if (it->is("Competitor")) {
      if (readXMLCompetitorDB(*it))
        personCount++;
    }
  }
}

void IOF30Interface::readClubList(gdioutput &gdi, const xmlobject &xo, int &clubCount) {
  if (!xo)
    return;

  string ver;
  xo.getObjectString("iofVersion", ver);
  if (!ver.empty() && ver > "3.0")
    gdi.addString("", 0, "Varning, okänd XML-version X#" + ver);

  xmlList xl;
  xo.getObjects(xl);

  xmlList::const_iterator it;
  for (it=xl.begin(); it != xl.end(); ++it) {
    if (it->is("Organisation")) {
      if (readOrganization(gdi, *it, true))
        clubCount++;
    }
  }
}

void IOF30Interface::prescanEntryList(xmlobject &xo, set<int> &definedStages) {
  definedStages.clear();

  xmlList pEntries;
  xo.getObjects("PersonEntry", pEntries);
  for (size_t k = 0; k < pEntries.size(); k++) {
    prescanEntry(pEntries[k], definedStages);
  }

  xo.getObjects("TeamEntry", pEntries);
  for (size_t k = 0; k < pEntries.size(); k++) {
    prescanEntry(pEntries[k], definedStages);
  }
}

void IOF30Interface::readEntryList(gdioutput &gdi, xmlobject &xo, bool removeNonexiting, 
                                   const set<int> &stageFilter,
                                   int &entRead, int &entFail, int &entRemoved) {
  string ver;
  entRemoved = 0;
  xo.getObjectString("iofVersion", ver);
  if (!ver.empty() && ver > "3.0")
    gdi.addString("", 0, "Varning, okänd XML-version X#" + ver);

  xmlobject xEvent = xo.getObject("Event");
  map<int, vector<LegInfo> > teamClassConfig;
  map<int, pair<wstring, int> > bibPatterns;
  oClass::extractBibPatterns(oe, bibPatterns);

  if (xEvent) {
    readEvent(gdi, xEvent, teamClassConfig);
  }

  vector<pRunner> allR;
  vector<pTeam> allT;
  oe.getRunners(0, 0, allR, false);
  for (size_t k = 0; k < allR.size(); k++) {
    if (allR[k]->getEntrySource() == entrySourceId)
      allR[k]->flagEntryTouched(false);
  }

  oe.getTeams(0, allT, false);
  for (size_t k = 0; k < allT.size(); k++) {
    if (allT[k]->getEntrySource() == entrySourceId)
      allT[k]->flagEntryTouched(false);
  }

  xmlList pEntries;
  xo.getObjects("PersonEntry", pEntries);
  map<int, vector< pair<int, int> > > personId2TeamLeg;
  for (size_t k = 0; k < pEntries.size(); k++) {
    if (readPersonEntry(gdi, pEntries[k], 0, teamClassConfig, stageFilter, personId2TeamLeg))
      entRead++;
    else
      entFail++;
  }

  xo.getObjects("TeamEntry", pEntries);
  for (size_t k = 0; k < pEntries.size(); k++) {
    xmlList races;
    pEntries[k].getObjects("Race", races);
    if (!matchStageFilter(stageFilter, races))
      continue; // Skip teams belonging to other stage

    setupClassConfig(0, pEntries[k], teamClassConfig);
  }

  // Get all classes, and use existing leg info  
  vector<pClass> allCls;
  oe.getClasses(allCls, false);
  for (size_t k = 0; k < allCls.size(); k++) {
    if (allCls[k]->getNumStages() > 1) {
      for (size_t j = 0; j < allCls[k]->getNumStages(); j++) {
        int number;
        int order;
        allCls[k]->splitLegNumberParallel(j, number, order);
        vector<LegInfo> &li = teamClassConfig[allCls[k]->getId()];
   
        if (size_t(number) >= li.size())
          li.resize(number+1);

        if (allCls[k]->getLegType(j) == LTExtra || allCls[k]->getLegType(j) == LTIgnore || allCls[k]->getLegType(j) == LTParallelOptional)
          li[number].setMaxRunners(order+1);
        else
          li[number].setMinRunners(order+1);
      }
    }
  }

  setupRelayClasses(teamClassConfig);

  for (size_t k = 0; k < pEntries.size(); k++) {
    if (readTeamEntry(gdi, pEntries[k], stageFilter, bibPatterns, teamClassConfig, personId2TeamLeg))
      entRead++;
    else
      entFail++;
  }

  bool hasMulti = false;
  for (map<int, vector< pair<int, int> > >::iterator it = personId2TeamLeg.begin();
                  it != personId2TeamLeg.end(); ++it) {
    if (it->second.size() > 1) {
      hasMulti = true;
      break;
    }
  }

  // Analyze equivalences of legs
  map<int, set< vector<int> > > classLegEqClasses;

  for (map<int, vector< pair<int, int> > >::iterator it = personId2TeamLeg.begin();
                  it != personId2TeamLeg.end(); ++it) {
    const vector< pair<int, int> > &teamLeg = it->second;
    if (teamLeg.empty())
      continue; // Should not happen
    int minLeg = teamLeg.front().second;
    int teamId = teamLeg.front().first;
    bool inconsistentTeam = false;

    for (size_t i = 1; i < teamLeg.size(); i++) {
      if (teamLeg[i].first != teamId) {
        inconsistentTeam = true;
        break;
      }
      if (teamLeg[i].second < minLeg)
        minLeg = teamLeg[i].second;
    }

    if (!inconsistentTeam) {
      pTeam t = oe.getTeam(teamId);
      if (t) {
        if (minLeg != teamLeg.front().second) {
          pRunner r = t->getRunner(teamLeg.front().second);
          t->setRunner(minLeg, r, true);
          t->synchronize(true);
        }

        // If multi, for each class, store how the legs was multiplied
        if (hasMulti) {
          vector<int> key(it->second.size());
          for (size_t j = 0; j < key.size(); j++) 
            key[j] = it->second[j].second;

          sort(key.begin(), key.end());
          classLegEqClasses[t->getClassId(false)].insert(key);
        }
      }
    }
  }

  for (map<int, set< vector<int> > >::const_iterator it = classLegEqClasses.begin();
       it != classLegEqClasses.end(); ++it) {
    const set< vector<int> > &legEq = it->second;
    pClass cls = oe.getClass(it->first);
    if (!cls)
      continue;
    bool invalid = false;
    vector<int> specification(cls->getNumStages(), -2);
    for (set< vector<int> >::const_iterator eqit = legEq.begin(); eqit != legEq.end(); ++eqit) {
      const vector<int> &eq = *eqit;
      for (size_t j = 0; j < eq.size(); j++) {
        size_t ix = eq[j];
        if (ix >= specification.size()) {
          invalid = true;
          break; // Internal error?
        }
        if (j == 0) { // Base leg
          if (specification[ix] >= 0) {
            invalid = true;
            break; // Inconsistent specification
          }
          else {
            specification[ix] = -1;
          }
        }
        else { // Duplicated leg
          if (specification[ix] == -1 || (specification[ix] >= 0 && specification[ix] != eq[0])) {
            invalid = true;
            break; // Inconsistent specification
          }
          else {
            specification[ix] = eq[0]; // Specify duplication of base leg
          }
        }
      }
    }
    if (invalid)
      continue;

    vector<pTeam> teams;
    oe.getTeams(it->first, teams);

    // Check that the guessed specification is compatible with all current teams
    for (size_t j = 0; j < specification.size(); j++) {
      if (specification[j] >= 0) {
        // Check that leg is not occupied
        for (size_t i = 0; i < teams.size(); i++) {
          if (teams[i]->getRunner(j) && teams[i]->getRunner(j)->getRaceNo() == 0) {
            invalid = true;
            break;
          }
        }
      }
    }

    if (invalid)
      continue;

    for (size_t j = 0; j < specification.size(); j++) {
      if (specification[j] >= 0) {
        cls->setLegRunner(j, specification[j]);
      }
    }
    oe.adjustTeamMultiRunners(cls);
  }

  if (removeNonexiting && entRead > 0) {
    for (size_t k = 0; k < allT.size(); k++) {
      if (allT[k]->getEntrySource() == entrySourceId && !allT[k]->isEntryTouched()) {
        gdi.addString("", 0, L"Tar bort X#" + allT[k]->getName());
        oe.removeTeam(allT[k]->getId());
        entRemoved++;
      }
      /*else {
        for (int i = 0; i < allT[k]->getNumRunners(); i++) {
          pRunner r = allT[k]->getRunner(i);
          if (r)
            r->flagEntryTouched(true);
        }
      }*/
    }

    vector<int> rids;
    for (size_t k = 0; k < allR.size(); k++) {
      if (allR[k]->getEntrySource() == entrySourceId && !allR[k]->isEntryTouched() && !allR[k]->getTeam()) {
        entRemoved++;
        gdi.addString("", 0, L"Tar bort X#" + allR[k]->getCompleteIdentification());
        rids.push_back(allR[k]->getId());
      }
    }
    if (!rids.empty())
      oe.removeRunner(rids);
  }
}


void IOF30Interface::readStartList(gdioutput &gdi, xmlobject &xo, int &entRead, int &entFail) {
  string ver;
  xo.getObjectString("iofVersion", ver);
  if (!ver.empty() && ver > "3.0")
    gdi.addString("", 0, "Varning, okänd XML-version X#" + ver);

  map<int, vector<LegInfo> > teamClassConfig;

  xmlobject xEvent = xo.getObject("Event");
  if (xEvent) {
    readEvent(gdi, xEvent, teamClassConfig);
  }

  xmlList cStarts;
  xo.getObjects("ClassStart", cStarts);

  struct RaceInfo {
    int courseId;
    int length;
    int climb;
    wstring startName;
  };

  for (size_t k = 0; k < cStarts.size(); k++) {
    xmlobject &xClassStart = cStarts[k];

    pClass pc = readClass(xClassStart.getObject("Class"),
                          teamClassConfig);
    int classId = pc ? pc->getId() : 0;


    map<int, RaceInfo> raceToInfo;

    xmlList courses;
    xClassStart.getObjects("Course", courses);
    for (size_t k = 0; k < courses.size(); k++) {
      int raceNo = courses[k].getObjectInt("raceNumber");
      if (raceNo > 0)
        raceNo--;
      RaceInfo &raceInfo = raceToInfo[raceNo];

      raceInfo.courseId = courses[k].getObjectInt("Id");
      raceInfo.length = courses[k].getObjectInt("Length");
      raceInfo.climb = courses[k].getObjectInt("Climb");
    }

    xmlList startNames;
    xClassStart.getObjects("StartName", startNames);
    for (size_t k = 0; k < startNames.size(); k++) {
      int raceNo = startNames[k].getObjectInt("raceNumber");
      if (raceNo > 0)
        raceNo--;
      RaceInfo &raceInfo = raceToInfo[raceNo];
      startNames[k].getObjectString(0, raceInfo.startName);
      pc->setStart(raceInfo.startName);
    }

    if (raceToInfo.size() == 1) {
      RaceInfo &raceInfo = raceToInfo.begin()->second;
      if (raceInfo.courseId > 0) {
        if (pc->getCourse() == 0) {
          pCourse crs = oe.addCourse(pc->getName(), raceInfo.length, raceInfo.courseId);
          crs->setStart(raceInfo.startName, false);
          crs->getDI().setInt("Climb", raceInfo.climb);
          pc->setCourse(crs);
          crs->synchronize();
        }
      }
    }
    else if (raceToInfo.size() > 1) {
    }

    xmlList xPStarts;
    xClassStart.getObjects("PersonStart", xPStarts);
    map<int, pair<wstring, int> > bibPatterns;
    oClass::extractBibPatterns(oe, bibPatterns);

    for (size_t k = 0; k < xPStarts.size(); k++) {
      if (readPersonStart(gdi, pc, xPStarts[k], 0, teamClassConfig))
        entRead++;
      else
        entFail++;
    }

    xmlList tEntries;
    xClassStart.getObjects("TeamStart", tEntries);
    for (size_t k = 0; k < tEntries.size(); k++) {
      setupClassConfig(classId, tEntries[k], teamClassConfig);
    }

    //setupRelayClasses(teamClassConfig);
    if (pc && teamClassConfig.count(pc->getId()) && !teamClassConfig[pc->getId()].empty()) {
      setupRelayClass(pc, teamClassConfig[pc->getId()]);
    }

    for (size_t k = 0; k < tEntries.size(); k++) {
      if (readTeamStart(gdi, pc, tEntries[k], bibPatterns, teamClassConfig))
        entRead++;
      else
        entFail++;
    }

    pc->synchronize();
  }
}

void IOF30Interface::readClassList(gdioutput &gdi, xmlobject &xo, int &entRead, int &entFail) {
  string ver;
  xo.getObjectString("iofVersion", ver);
  if (!ver.empty() && ver > "3.0")
    gdi.addString("", 0, "Varning, okänd XML-version X#" + ver);

  map<int, vector<LegInfo> > teamClassConfig;

  xmlobject xEvent = xo.getObject("Event");
  if (xEvent) {
    readEvent(gdi, xEvent, teamClassConfig);
  }

  xmlList cClass;
  xo.getObjects("Class", cClass);


  for (size_t k = 0; k < cClass.size(); k++) {
    xmlobject &xClass = cClass[k];

    pClass pc = readClass(xClass, teamClassConfig);

    if (pc)
      entRead++;
    else
      entFail++;

    if (pc && teamClassConfig.count(pc->getId()) && !teamClassConfig[pc->getId()].empty()) {
      setupRelayClass(pc, teamClassConfig[pc->getId()]);
    }

    pc->synchronize();
  }
}

void IOF30Interface::readEventList(gdioutput &gdi, xmlobject &xo) {
  if (!xo)
    return;

  string ver;
  xo.getObjectString("iofVersion", ver);
  if (!ver.empty() && ver > "3.0")
    gdi.addString("", 0, "Varning, okänd XML-version X#" + ver);

  xmlList xl;
  xo.getObjects(xl);

  xmlList::const_iterator it;
  map<int, vector<LegInfo> > teamClassConfig;
  for (it=xl.begin(); it != xl.end(); ++it) {
    if (it->is("Event")) {
      readEvent(gdi, *it, teamClassConfig);
      return;
    }
  }
}

void IOF30Interface::readEvent(gdioutput &gdi, const xmlobject &xo,
                               map<int, vector<LegInfo> > &teamClassConfig) {

  wstring name;
  xo.getObjectString("Name", name);
  oe.setName(name);

  int id = xo.getObjectInt("Id");
  if (id>0) {
    oe.setExtIdentifier(id);
    entrySourceId = id;
  }
  else {
    entrySourceId = 1; // Use this as a default number for "imported entries"
  }

  xmlobject date = xo.getObject("StartTime");

  if (date) {
    wstring dateStr;
    date.getObjectString("Date", dateStr);
    oe.setDate(dateStr);
    wstring timeStr;
    date.getObjectString("Time", timeStr);
    if (!timeStr.empty()) {
      int t = convertAbsoluteTimeISO(timeStr);
      if (t >= 0 && oe.getNumRunners() == 0) {
        int zt = t - 3600;
        if (zt < 0)
          zt += 3600*24;
        oe.setZeroTime(formatTimeHMS(zt));
      }
    }
    //oe.setZeroTime(...);
  }

  xmlobject xOrg = xo.getObject("Organiser");
  oDataInterface DI = oe.getDI();

  if (xOrg) {
    wstring name;
    xOrg.getObjectString("Name", name);
    if (name.length() > 0)
      DI.setString("Organizer", name);

    xmlobject address = xOrg.getObject("Address");

    wstring tmp;

    if (address) {
      DI.setString("CareOf", address.getObjectString("CareOf", tmp));
      DI.setString("Street", address.getObjectString("Street", tmp));
      wstring city, zip, state;
      address.getObjectString("City", city);
      address.getObjectString("ZipCode", zip);
      address.getObjectString("State", state);
      if (state.empty())
        DI.setString("Address", zip + L" " + city);
      else
        DI.setString("Address", state + L", " + zip + L" " + city);
    }

    xmlList xContact;
    xOrg.getObjects("Contact", xContact);

    wstring phone;
    for (size_t k = 0; k < xContact.size(); k++) {
      string type;
      xContact[k].getObjectString("type", type);
      wstring c;
      xContact[k].getObjectString(0, c);

      if (type == "PhoneNumber" || "MobilePhoneNumber")
        phone += phone.empty() ? c : L", " + c;
      else if (type == "EmailAddress")
        DI.setString("EMail", c);
      else if (type == "WebAddress")
        DI.setString("Homepage", c);
    }
    if (!phone.empty())
      DI.setString("Phone", phone);
  }

  wstring account;
  xo.getObjectString("Account", account);
  if (!account.empty())
    DI.setString("Account", account);

  xmlList xClass;
  xo.getObjects("Class", xClass);
  for (size_t k = 0; k < xClass.size(); k++)
    readClass(xClass[k], teamClassConfig);

  if (!feeStatistics.empty()) {
    set<int> fees;
    set<int> factors;
    for (size_t i = 0; i < feeStatistics.size(); i++) {
      int fee = int(100 * feeStatistics[i].fee);
      int factor = int(100 * feeStatistics[i].lateFactor) - 100;
      fees.insert(fee);
      if (factor > 0)
        factors.insert(factor);
    }
    int n = 0, y = 0, e = 0;

    if (fees.size() >= 3) {
      y = *fees.begin();
      fees.erase(fees.begin());
      n = *fees.begin();
      fees.erase(fees.begin());
      e = *fees.rbegin();
    }
    else if (fees.size() == 2) {
      y = *fees.begin();
      fees.erase(fees.begin());
      e = n = *fees.begin();
    }
    else if (fees.size() == 1) {
      e = n = y = *fees.begin();
    }

    if (n > 0) {
      DI.setInt("EliteFee", oe.interpretCurrency(double(e) * 0.01, L""));
      DI.setInt("EntryFee", oe.interpretCurrency(double(n) * 0.01, L""));
      DI.setInt("YouthFee", oe.interpretCurrency(double(y) * 0.01, L""));
    }

    if (!factors.empty()) {
      wchar_t lf[16];
      swprintf_s(lf, L"%d %%", *factors.rbegin());
      DI.setString("LateEntryFactor", lf);
    }
  }
  oe.synchronize();
}

void IOF30Interface::setupClassConfig(int classId, const xmlobject &xTeam, map<int, vector<LegInfo> > &teamClassConfig) {

  // Get class
  xmlobject xClass = xTeam.getObject("Class");
  if (xClass) {
    pClass pc = readClass(xClass, teamClassConfig);
    classId = pc->getId();
  }
  vector<LegInfo> &teamClass = teamClassConfig[classId];

  // Get team entriess
  xmlList xEntries;
  xTeam.getObjects("TeamEntryPerson", xEntries);
  for (size_t k = 0; k < xEntries.size(); k++) {
    int leg = xEntries[k].getObjectInt("Leg");
    int legorder = xEntries[k].getObjectInt("LegOrder");
    leg = max(0, leg - 1);
    legorder = max(1, legorder);
    if (int(teamClass.size()) <= leg)
      teamClass.resize(leg + 1);
    teamClass[leg].setMaxRunners(legorder);
  }

  // Get team starts
  xmlList xMemberStarts;
  xTeam.getObjects("TeamMemberStart", xMemberStarts);
  for (size_t k = 0; k < xMemberStarts.size(); k++) {
    xmlList xStarts;
    xMemberStarts[k].getObjects("Start", xStarts);
    for (size_t j = 0; j < xStarts.size(); j++) {
      int leg = xStarts[j].getObjectInt("Leg");
      int legorder = xStarts[j].getObjectInt("LegOrder");
      leg = max(0, leg - 1);
      legorder = max(1, legorder);
      if (int(teamClass.size()) <= leg)
        teamClass.resize(leg + 1);
      teamClass[leg].setMaxRunners(legorder);
    }
  }
}

pTeam IOF30Interface::readTeamEntry(gdioutput &gdi, xmlobject &xTeam,
                                    const set<int> &stageFilter,
                                    map<int, pair<wstring, int> > &bibPatterns,
                                    const map<int, vector<LegInfo> > &teamClassConfig,
                                    map<int, vector< pair<int, int> > > &personId2TeamLeg) {

  xmlList races;
  xTeam.getObjects("Race", races);
  if (!matchStageFilter(stageFilter, races))
    return 0;
  
  bool newTeam;
  pTeam t = getCreateTeam(gdi, xTeam, newTeam);

  if (!t)
    return 0;

  // Class
  map<int, vector<LegInfo> > localTeamClassConfig;
  pClass pc = readClass(xTeam.getObject("Class"), localTeamClassConfig);

  if (pc && (t->getClassId(false) == 0 || !t->hasFlag(oAbstractRunner::FlagUpdateClass)) ) { 
    t->setClassId(pc->getId(), false);
  }
  wstring bib;
  xTeam.getObjectString("BibNumber", bib);
  wchar_t pat[32];
  int no = oClass::extractBibPattern(bib, pat);
  if (no > 0 && t->getBib().empty())
    t->setBib(bib, no, true, false);
  else if (newTeam) {
    pair<int, wstring> autoBib = pc->getNextBib(bibPatterns);
    if (autoBib.first > 0) {
      t->setBib(autoBib.second, autoBib.first, true, false);
    }
  }

  oDataInterface di = t->getDI();
  if (newTeam) {
    wstring entryTime;
    xTeam.getObjectString("EntryTime", entryTime);
    di.setDate("EntryDate", entryTime);

    size_t tpos = entryTime.find_first_of(L"T");
    if (tpos != -1) {
      wstring timeString = entryTime.substr(tpos+1);
      int t = convertAbsoluteTimeISO(timeString);
      if (t >= 0)
        di.setInt("EntryTime", t);
    }
  }

  double fee = 0, paid = 0, taxable = 0, percentage = 0;
  wstring currency;
  xmlList xAssigned;
  xTeam.getObjects("AssignedFee", xAssigned);
  for (size_t j = 0; j < xAssigned.size(); j++) {
    getAssignedFee(xAssigned[j], fee, paid, taxable, percentage, currency);
  }
  fee += fee * percentage; // OLA / Eventor stupidity

  di.setInt("Fee", oe.interpretCurrency(fee, currency));
  di.setInt("Paid", oe.interpretCurrency(paid, currency));
  di.setInt("Taxable", oe.interpretCurrency(fee, currency));

  xmlList xEntries;
  xTeam.getObjects("TeamEntryPerson", xEntries);

  set<int> noFilter;
  for (size_t k = 0; k<xEntries.size(); k++) {
    readPersonEntry(gdi, xEntries[k], t, teamClassConfig, noFilter, personId2TeamLeg);
  }

  t->synchronize();
  return t;
}

pTeam IOF30Interface::readTeamStart(gdioutput &gdi, pClass pc, xmlobject &xTeam,
                                    map<int, pair<wstring, int> > &bibPatterns,
                                    const map<int, vector<LegInfo> > &teamClassConfig) {
  bool newTeam;
  pTeam t = getCreateTeam(gdi, xTeam, newTeam);

  if (!t)
    return 0;

  // Class
  if (pc && (t->getClassId(false) == 0 || !t->hasFlag(oAbstractRunner::FlagUpdateClass)) )
    t->setClassId(pc->getId(), false);

  wstring bib;
  xTeam.getObjectString("BibNumber", bib);
  wchar_t pat[32];
  int no = oClass::extractBibPattern(bib, pat);
  if (no > 0 && t->getBib().empty())
    t->setBib(bib, no, true, false);
  else if (newTeam){
    pair<int, wstring> autoBib = pc->getNextBib(bibPatterns);
    if (autoBib.first > 0) {
      t->setBib(autoBib.second, autoBib.first, true, false);
    }
  }
  xmlList xEntries;
  xTeam.getObjects("TeamMemberStart", xEntries);
  
  for (size_t k = 0; k<xEntries.size(); k++) {
    readPersonStart(gdi, pc, xEntries[k], t, teamClassConfig);
  }

  t->synchronize();
  return t;
}

pTeam IOF30Interface::getCreateTeam(gdioutput &gdi, const xmlobject &xTeam, bool &newTeam) {
  newTeam = false;
  wstring name;
  xTeam.getObjectString("Name", name);

  if (name.empty())
    return 0;

  int id = xTeam.getObjectInt("Id");
  pTeam t = 0;

  if (id)
    t = oe.getTeam(id);
  else {
    t = oe.getTeamByName(name);
    // XXX Check class
  }
  if (!t) {
    if (id > 0) {
      oTeam tr(&oe, id);
      t = oe.addTeam(tr, true);
    }
    else {
      oTeam tr(&oe);
      t = oe.addTeam(tr, true);
    }
    newTeam = true;
  }

  if (!t)
    return 0;

  t->setEntrySource(entrySourceId);
  t->flagEntryTouched(true);  
  if (t->getName().empty() || !t->hasFlag(oAbstractRunner::FlagUpdateName))
    t->setName(name, false);

  // Club
  pClub c = 0;
  xmlList xOrgs;
  xTeam.getObjects("Organisation", xOrgs);
  if (xOrgs.empty())
    xTeam.getObjects("Organization", xOrgs);

  for (size_t k = 0; k < xOrgs.size(); k++) {
    if (c == 0)
      c = readOrganization(gdi, xOrgs[k], false);
    else
      readOrganization(gdi, xOrgs[k], false);// Just include in competition
  }

  if (c)
    t->setClubId(c->getId());

  return t;
}

int IOF30Interface::getIndexFromLegPos(int leg, int legorder, const vector<LegInfo> &setup) {
  int ix = 0;
  for (int k = 0; k < leg - 1; k++)
    ix += k < int(setup.size()) ? max(setup[k].maxRunners, 1) : 1;
  if (legorder > 0)
    ix += legorder - 1;
  return ix;
}

void IOF30Interface::prescanEntry(xmlobject &xo, set<int> &stages) {
  xmlList races;
  xo.getObjects("RaceNumber", races);
  if (races.empty())
    xo.getObjects("Race", races); // For unclear reason the attribute is called Race for teams and RaceNumber for persons.

  if (races.empty())
    stages.insert(-1);// All
  else {
    for (auto &race : races) {
      int r = race.getInt();
      if (r > 0)
        stages.insert(r);
    }
  }
}

bool IOF30Interface::matchStageFilter(const set<int> &stageFilter, const xmlList &races) {
  if (stageFilter.empty() || races.empty())
    return true;

  for (auto &r : races) {
    if (stageFilter.count(r.getInt()))
      return true;
  }

  return false;
}

pRunner IOF30Interface::readPersonEntry(gdioutput &gdi, xmlobject &xo, pTeam team,
                                        const map<int, vector<LegInfo> > &teamClassConfig,
                                        const set<int> &stageFilter, 
                                        map<int, vector< pair<int, int> > > &personId2TeamLeg) {
  
  xmlList races;
  xo.getObjects("RaceNumber", races);
  if (!matchStageFilter(stageFilter, races))
    return 0;
  
  xmlobject xPers = xo.getObject("Person");
  // Card
  const int cardNo = xo.getObjectInt("ControlCard");

  pRunner r = 0;

  if (xPers)
    r = readPerson(gdi, xPers);

  if (cardNo > 0 && r == 0 && team) {
    // We got no person, but a card number. Add the runner anonymously.
    r = oe.addRunner(lang.tl(L"N.N."), team->getClubId(), team->getClassId(false), cardNo, 0, false);
    r->flagEntryTouched(true);
    r->setEntrySource(entrySourceId);
    r->synchronize();
  }

  if (r == 0)
    return 0;

  // Club
  pClub c = readOrganization(gdi, xo.getObject("Organisation"), false);
  if (!c)
    c = readOrganization(gdi, xo.getObject("Organization"), false);

  if (c)
    r->setClubId(c->getId());

  // Class
  map<int, vector<LegInfo> > localTeamClassConfig;
  pClass pc = readClass(xo.getObject("Class"), localTeamClassConfig);

  if (pc && (r->getClassId(false) == 0 || !r->hasFlag(oAbstractRunner::FlagUpdateClass)) )
    r->setClassId(pc->getId(), false);

  if (team) {
    int leg = xo.getObjectInt("Leg");
    int legorder = xo.getObjectInt("LegOrder");
    int legindex = max(0, leg - 1);
    map<int, vector<LegInfo> >::const_iterator res = teamClassConfig.find(team->getClassId(false));
    if (res != teamClassConfig.end()) {
      legindex = getIndexFromLegPos(leg, legorder, res->second);
    }

    if (personId2TeamLeg.find(r->getId()) == personId2TeamLeg.end()) {
      if (team->getClassRef(false))
        legindex = team->getClassRef(false)->getLegRunner(legindex);

      // Ensure unique
      team->setRunner(legindex, r, false);
      if (r->getClubId() == 0)
        r->setClubId(team->getClubId());
    }
    personId2TeamLeg[r->getId()].push_back(make_pair(team->getId(), legindex));
  }

  // Card
  if (cardNo > 0)
    r->setCardNo(cardNo, false);

  oDataInterface di = r->getDI();

  wstring entryTime;
  xo.getObjectString("EntryTime", entryTime);
  di.setDate("EntryDate", entryTime);
  size_t tpos = entryTime.find_first_of(L"T");
  if (tpos != -1) {
    wstring timeString = entryTime.substr(tpos+1);
    int t = convertAbsoluteTimeISO(timeString);
    if (t >= 0)
      di.setInt("EntryTime", t);
  }


  double fee = 0, paid = 0, taxable = 0, percentage = 0;
  wstring currency;
  xmlList xAssigned;
  xo.getObjects("AssignedFee", xAssigned);
  for (size_t j = 0; j < xAssigned.size(); j++) {
    getAssignedFee(xAssigned[j], fee, paid, taxable, percentage, currency);
  }
  fee += fee * percentage; // OLA / Eventor stupidity

  di.setInt("Fee", oe.interpretCurrency(fee, currency));
  di.setInt("Paid", oe.interpretCurrency(paid, currency));
  di.setInt("Taxable", oe.interpretCurrency(fee, currency));


  // StartTimeAllocationRequest
  xmlobject sar = xo.getObject("StartTimeAllocationRequest");
  if (sar) {
    string type;
    sar.getObjectString("type", type);
    if (type == "groupedWithRef") {
      xmlobject pRef = sar.getObject("Person");
      if (pRef) {
        wstring sid;
        pRef.getObjectString("Id", sid);
        __int64 extId = oBase::converExtIdentifierString(sid);
        int pid = oBase::idFromExtId(extId);
        pRunner rRef = oe.getRunner(pid, 0);
        if (rRef && rRef->getExtIdentifier() == extId) {
          rRef->setReference(r->getId());
        }
        r->setReference(pid);
      }
    }
  }

  r->synchronize();
  return r;
}

pRunner IOF30Interface::readPersonStart(gdioutput &gdi, pClass pc, xmlobject &xo, pTeam team,
                                        const map<int, vector<LegInfo> > &teamClassConfig) {
  xmlobject xPers = xo.getObject("Person");
  pRunner r = 0;
  if (xPers)
    r = readPerson(gdi, xPers);
  if (r == 0)
    return 0;

  // Club
  pClub c = readOrganization(gdi, xo.getObject("Organisation"), false);
  if (!c)
    c = readOrganization(gdi, xo.getObject("Organization"), false);

  if (c)
    r->setClubId(c->getId());

  xmlList starts;
  xo.getObjects("Start", starts);

  for (size_t k = 0; k < starts.size(); k++) {
    int race = starts[k].getObjectInt("raceNumber");
    pRunner rRace = r;
    if (race > 1 && r->getNumMulti() > 0) {
      pRunner rr = r->getMultiRunner(race - 1);
      if (rr)
        rRace = rr;
    }
    if (rRace) {
      // Card
      int cardNo = starts[k].getObjectInt("ControlCard");
      if (cardNo > 0)
        rRace->setCardNo(cardNo, false);

      xmlobject startTime = starts[k].getObject("StartTime");

      if (team) {
        int leg = starts[k].getObjectInt("Leg");
        int legorder = starts[k].getObjectInt("LegOrder");
        int legindex = max(0, leg - 1);
        map<int, vector<LegInfo> >::const_iterator res = teamClassConfig.find(team->getClassId(false));
        if (res != teamClassConfig.end()) {
          legindex = getIndexFromLegPos(leg, legorder, res->second);
        }
        team->setRunner(legindex, rRace, false);
        if (rRace->getClubId() == 0)
          rRace->setClubId(team->getClubId());

        if (startTime && pc) {
          pc->setStartType(legindex, STDrawn, false);

        }
      }

      wstring bib;
      starts[k].getObjectString("BibNumber", bib);
      rRace->getDI().setString("Bib", bib);

      rRace->setStartTime(parseISO8601Time(startTime), true, false);
    }
  }

  if (pc && (r->getClassId(false) == 0 || !r->hasFlag(oAbstractRunner::FlagUpdateClass)) )
    r->setClassId(pc->getId(), true);

  r->synchronize();
  return r;
}



pRunner IOF30Interface::readPerson(gdioutput &gdi, const xmlobject &person) {

  xmlobject pname = person.getObject("Name");

  wstring name;
  
  if (pname) {
    wstring given, family;
    //name = getFirst(pname.getObjectString("Given", given), 2)+ " " +pname.getObjectString("Family", family);
    name = pname.getObjectString("Family", family) + L", " + getFirst(pname.getObjectString("Given", given), 2);
  }
  else {
    name = lang.tl("N.N.");
  }

  wstring sid;
  person.getObjectString("Id", sid);
  __int64 extId = oBase::converExtIdentifierString(sid);
  int pid = oBase::idFromExtId(extId);
  pRunner r = 0;

  if (pid) {
    r = oe.getRunner(pid, 0);
    while (r) { // Check that the exact match is OK
      if (extId == r->getExtIdentifier())
        break;
      pid++;
      r = oe.getRunner(pid, 0);
    }

    if (r) {
      // Check that a with this id runner does not happen to exist with a different source
      if (entrySourceId>0 && r->getEntrySource() != entrySourceId) {
        r = 0;
        pid = 0;
      }
      else if (entrySourceId == 0) {
        wstring canName = canonizeName(name.c_str());
        wstring canOldName = canonizeName(r->getName().c_str());
        if (canName != canOldName) {
          r = 0;
          pid = 0;
        }
      }
    }
  }

  if (!r) {
    if ( pid > 0) {
      oRunner or(&oe, pid);
      r = oe.addRunner(or, true);
    }
    else {
      oRunner or(&oe);
      r = oe.addRunner(or, true);
    }
  }

  r->setEntrySource(entrySourceId);
  r->flagEntryTouched(true);
 
  if (!r->hasFlag(oAbstractRunner::FlagUpdateName)) {
    r->setName(name, false);
  }

  r->setExtIdentifier(extId);

  oDataInterface DI=r->getDI();
  wstring tmp;

  PersonSex s = interpretSex(person.getObjectString("sex", tmp));
  if (s != sUnknown)
    r->setSex(s);
  person.getObjectString("BirthDate", tmp);
  if (tmp.length()>=4) {
    tmp = tmp.substr(0, 4);
    r->setBirthYear(_wtoi(tmp.c_str()));
  }

  getNationality(person.getObject("Nationality"), DI);

  return r;
}

pClub IOF30Interface::readOrganization(gdioutput &gdi, const xmlobject &xclub, bool saveToDB) {
  if (!xclub)
    return 0;
  wstring clubIdS;
  xclub.getObjectString("Id", clubIdS);
  __int64 extId = oBase::converExtIdentifierString(clubIdS);
  int clubId = oBase::idFromExtId(extId);
  wstring name, shortName;
  xclub.getObjectString("Name", name);
  xclub.getObjectString("ShortName", shortName);

  if (shortName.length() > 4 && shortName.length() < name.length())
    swap(name, shortName);

  if (name.length()==0 || !IsCharAlphaNumeric(name[0]))
    return 0;

  pClub pc=0;

  if ( !saveToDB ) {
    if (clubId)
      pc = oe.getClubCreate(clubId, name);

    if (!pc) return false;
  }
  else {
    pc = new oClub(&oe, clubId);
    //pc->setID->Id = clubId;
  }

  pc->setName(name);

  pc->setExtIdentifier(extId);

  oDataInterface DI=pc->getDI();

  wstring tmp;

  int district = xclub.getObjectInt("ParentOrganisationId");
  if (district > 0)
    DI.setInt("District", district);

  xmlobject address = xclub.getObject("Address");

  if (shortName.length() <= 4)
    DI.setString("ShortName", shortName);

  wstring str;

  if (address) {
    DI.setString("CareOf", address.getObjectString("CareOf", tmp));
    DI.setString("Street", address.getObjectString("Street", tmp));
    DI.setString("City", address.getObjectString("City", tmp));
    DI.setString("ZIP", address.getObjectString("ZipCode", tmp));
    DI.setString("State", address.getObjectString("State", tmp));
    getNationality(address.getObject("Country"), DI);
  }

  xmlList xContact;
  xclub.getObjects("Contact", xContact);

  wstring phone;
  for (size_t k = 0; k < xContact.size(); k++) {
    string type;
    xContact[k].getObjectString("type", type);
    wstring c;
    xContact[k].getObjectString(0, c);

    if (type == "PhoneNumber" || type == "MobilePhoneNumber")
      phone += phone.empty() ? c : L", " + c;
    else if (type == "EmailAddress")
      DI.setString("EMail", c);
  }
  DI.setString("Phone", phone);

  getNationality(xclub.getObject("Country"), DI);

  xclub.getObjectString("type", str);
  if (!str.empty())
    DI.setString("Type", str);

  if (saveToDB) {
    oe.getRunnerDatabase().importClub(*pc, false);
    delete pc;
  }
  else {
    pc->synchronize();
  }

  return pc;
}

void IOF30Interface::getNationality(const xmlobject &xCountry, oDataInterface &di) {
  if (xCountry) {
    wstring code, country;

    xCountry.getObjectString("code", code);
    xCountry.getObjectString(0, country);

    if (!code.empty())
        di.setString("Nationality", code);

    if (!country.empty())
        di.setString("Country", country);
  }
}

void IOF30Interface::getAmount(const xmlobject &xAmount, double &amount, wstring &currency) {
  amount = 0; // Do no clear currency. It is filled in where found (and assumed to be constant)
  if (xAmount) {
    string tmp;
    xAmount.getObjectString(0, tmp);
    amount = atof(tmp.c_str());
    xAmount.getObjectString("currency", currency);
  }
}

void IOF30Interface::getFeeAmounts(const xmlobject &xFee, double &fee, double &taxable, double &percentage, wstring &currency) {
  xmlobject xAmount = xFee.getObject("Amount");
  xmlobject xPercentage = xFee.getObject("Percentage"); // Eventor / OLA stupidity
  if (xPercentage) {
    string tmp;
    xPercentage.getObjectString(0, tmp);
    percentage = atof(tmp.c_str()) * 0.01;
  }
  else
    getAmount(xAmount, fee, currency);
  getAmount(xFee.getObject("TaxableAmount"), taxable, currency);
}

void IOF30Interface::getAssignedFee(const xmlobject &xFee, double &fee, double &paid, double &taxable, double &percentage, wstring &currency) {
  currency.clear();
  if (xFee) {
    getFeeAmounts(xFee.getObject("Fee"), fee, taxable, percentage, currency);
    getAmount(xFee.getObject("PaidAmount"), paid, currency);
  }
}

void IOF30Interface::getFee(const xmlobject &xFee, FeeInfo &fee) {
  getFeeAmounts(xFee, fee.fee, fee.taxable, fee.percentage, fee.currency);

  xFee.getObjectString("ValidFromTime", fee.fromTime);
  xFee.getObjectString("ValidToTime", fee.toTime);

  xFee.getObjectString("FromDateOfBirth", fee.fromBirthDate);
  xFee.getObjectString("ToDateOfBirth", fee.toBirthDate);
}

void IOF30Interface::writeAmount(xmlparser &xml, const char *tag, int amount) const {
  if (amount > 0) {
    wstring code = oe.getDCI().getString("CurrencyCode");
    if (code.empty())
      xml.write(tag, oe.formatCurrency(amount, false));
    else
      xml.write(tag, "currency", code, oe.formatCurrency(amount, false));
  }
}

void IOF30Interface::writeAssignedFee(xmlparser &xml, const oAbstractRunner &tr, int paidForCard) const {
  const oDataConstInterface dci = tr.getDCI();
  int fee = dci.getInt("Fee");
  int taxable = dci.getInt("Taxable");
  int paid = dci.getInt("Paid");

  if (fee == 0 && taxable == 0 && paid == 0)
    return;

  if (paid >= paidForCard) {
    paid -= paidForCard; // Included in card service fee
  }
  const oClass *pc = tr.getClassRef(false);
  if (!splitLateFee || !pc || !tr.hasLateEntryFee()) {
    xml.startTag("AssignedFee");
     string type = tr.hasLateEntryFee() ? "Late" : "Normal";
     xml.startTag("Fee", "type", type);
      xml.write("Name", L"Entry fee");
      writeAmount(xml, "Amount", fee);
      writeAmount(xml, "TaxableAmount", taxable);
     xml.endTag();

    writeAmount(xml, "PaidAmount", paid);
    xml.endTag();
  }
  else {
    int normalFee = pc->getDCI().getInt("ClassFee");
  
    int feeSplit[2] = {fee, 0};
    int paidSplit[2] = {paid, 0};
    if (normalFee > 0) {
      feeSplit[0] = min<int>(normalFee, fee);
      feeSplit[1] = max<int>(0, fee - feeSplit[0]);

      paidSplit[0] = min<int>(paid, feeSplit[0]);
      paidSplit[1] = max<int>(0, paid - paidSplit[0]);
    }

    for (int ft = 0; ft < 2; ft++) {
      xml.startTag("AssignedFee");
       string type = ft == 1 ? "Late" : "Normal";
       xml.startTag("Fee", "type", type);
        xml.write("Name", L"Entry fee");
        writeAmount(xml, "Amount", feeSplit[ft]);
        if (ft == 0)
          writeAmount(xml, "TaxableAmount", taxable);
       xml.endTag();

      writeAmount(xml, "PaidAmount", paidSplit[ft]);
      xml.endTag();
    }
  }
}

void IOF30Interface::writeRentalCardService(xmlparser &xml, int cardFee, bool paid) const {
  xml.startTag("ServiceRequest"); {

    xml.startTag("Service", "type", "RentalCard"); {
      xml.write("Name", L"Card Rental");
    }
    xml.endTag();

    xml.write("RequestedQuantity", L"1");

    xml.startTag("AssignedFee"); {
      xml.startTag("Fee"); {
        xml.write("Name", L"Card Rental Fee");
        writeAmount(xml, "Amount", cardFee);
      }
      xml.endTag();

      if (paid) {
        writeAmount(xml, "PaidAmount", cardFee);
      }
    }
    xml.endTag();
  }
  xml.endTag();
}

void IOF30Interface::getAgeLevels(const vector<FeeInfo> &fees, const vector<int> &ix,
                                  int &normalIx, int &redIx, wstring &youthLimit, wstring &seniorLimit) {
  assert(!ix.empty());
  if (ix.size() == 1) {
    normalIx = ix[0];
    redIx = ix[0];
    return;
  }
  else {
    normalIx = redIx = ix[0];
    for (size_t k = 0; k < ix.size(); k++) {
      if (fees[ix[k]] < fees[redIx])
        redIx = ix[k];
      if (fees[normalIx] < fees[ix[k]])
        normalIx = ix[k];

      const wstring &to = fees[ix[k]].toBirthDate;
      const wstring &from = fees[ix[k]].fromBirthDate;

      if (!from.empty() && (youthLimit.empty() || youthLimit > from))
        youthLimit = from;

      if (!to.empty() && (seniorLimit.empty() || seniorLimit > to))
        seniorLimit = to;
    }
  }
}

int getAgeFromDate(const wstring &date) {
  int y = getThisYear();
  SYSTEMTIME st;
  convertDateYMS(date, st, false);
  if (st.wYear > 1900)
    return y - st.wYear;
  else
    return 0;
}

void IOF30Interface::FeeInfo::add(IOF30Interface::FeeInfo &fi) {
  fee += fi.fee;
  fee += fee*percentage;

  taxable += fi.taxable;

  if (fi.toTime.empty() || (fi.toTime > fromTime && !fromTime.empty())) {
    fi.toTime = fromTime;
    if (!fi.toTime.empty()) {
      SYSTEMTIME st;
      convertDateYMS(fi.toTime, st, false);
      __int64 sec = SystemTimeToInt64Second(st);
      sec -= 3600;
      fi.toTime = convertSystemDate(Int64SecondToSystemTime(sec));
    }
  }
  //if (fi.fromTime.empty() || (fi.fromTime < toTime && !toTime.empty()))
  //  fi.fromTime = toTime;
}

pClass IOF30Interface::readClass(const xmlobject &xclass,
                                 map<int, vector<LegInfo> > &teamClassConfig) {
  if (!xclass)
    return 0;
  int classId = xclass.getObjectInt("Id");
  wstring name, shortName, longName;
  xclass.getObjectString("Name", name);
  xclass.getObjectString("ShortName", shortName);

  if (!shortName.empty()) {
    longName = name;
    name = shortName;
  }

  pClass pc = 0;

  if (classId) {
    pc = oe.getClass(classId);

    if (!pc) {
      oClass c(&oe, classId);
      pc = oe.addClass(c);
    }
  }
  else
    pc = oe.addClass(name);

  oDataInterface DI = pc->getDI();

  if (!longName.empty()) {
    pc->setName(name);
    DI.setString("LongName", longName);
  }
  else {
    if (pc->getName() != name && DI.getString("LongName") != name)
      pc->setName(name);
  }
  xmlList legs;
  xclass.getObjects("Leg", legs);
  if (!legs.empty()) {
    vector<LegInfo> &legInfo = teamClassConfig[pc->getId()];
    if (legInfo.size() < legs.size())
      legInfo.resize(legs.size());

    for (size_t k = 0; k < legs.size(); k++) {
      legInfo[k].setMaxRunners(legs[k].getObjectInt("maxNumberOfCompetitors"));
      legInfo[k].setMinRunners(legs[k].getObjectInt("minNumberOfCompetitors"));
    }
  }

  wstring tmp;
  // Status
  xclass.getObjectString("Status", tmp);

  if (tmp == L"Invalidated")
    DI.setString("Status", L"I"); // No refund
  else if (tmp == L"InvalidatedNoFee")
    DI.setString("Status", L"IR"); // Refund

  // No timing
  xclass.getObjectString("resultListMode", tmp);
  if (tmp == L"UnorderedNoTimes")
    pc->setNoTiming(true);

  int minAge = xclass.getObjectInt("minAge");
  if (minAge > 0)
    DI.setInt("LowAge", minAge);

  int highAge = xclass.getObjectInt("maxAge");
  if (highAge > 0)
    DI.setInt("HighAge", highAge);

  xclass.getObjectString("sex", tmp);
  if (!tmp.empty())
    DI.setString("Sex", tmp);

  xmlobject type = xclass.getObject("ClassType");
  if (type) {
    DI.setString("ClassType", type.getObjectString("Id", tmp));
  }

  // XXX we only care about the existance of one race class
  xmlobject raceClass = xclass.getObject("RaceClass");

  if (raceClass) {
    xmlList xFees;
    raceClass.getObjects("Fee", xFees);
    if (xFees.size() > 0) {
      vector<FeeInfo> fees(xFees.size());
      int feeIx = 0;
      int feeLateIx = 0;
      int feeRedIx = 0;
      int feeRedLateIx = 0;

      map<wstring, vector<int> > feePeriods;
      for (size_t k = 0; k < xFees.size(); k++) {
        getFee(xFees[k], fees[k]);
      }

      for (size_t k = 0; k < fees.size(); k++) {
        for (size_t j = k+1; j < fees.size(); j++) {
          if (fees[k].includes(fees[j]))
            fees[j].add(fees[k]);
          if (fees[j].includes(fees[k]))
            fees[k].add(fees[j]);
        }
        feePeriods[fees[k].getDateKey()].push_back(k);
      }

      wstring youthLimit;
      wstring seniorLimit;

      vector<int> &earlyEntry = feePeriods.begin()->second;
      getAgeLevels(fees, earlyEntry, feeIx, feeRedIx, youthLimit, seniorLimit);
      const wstring &lastODate = fees[earlyEntry[0]].toTime;
      if (!lastODate.empty()) {
        oe.getDI().setDate("OrdinaryEntry", lastODate);
      }
      vector<int> &lateEntry = feePeriods.rbegin()->second;
      getAgeLevels(fees, lateEntry, feeLateIx, feeRedLateIx, youthLimit, seniorLimit);

      if (!youthLimit.empty())
        oe.getDI().setInt("YouthAge", getAgeFromDate(youthLimit));

      if (!seniorLimit.empty())
        oe.getDI().setInt("SeniorAge", getAgeFromDate(seniorLimit));

      DI.setInt("ClassFee", oe.interpretCurrency(fees[feeIx].fee, fees[feeIx].currency));
      DI.setInt("HighClassFee", oe.interpretCurrency(fees[feeLateIx].fee, fees[feeLateIx].currency));

      DI.setInt("ClassFeeRed", oe.interpretCurrency(fees[feeRedIx].fee, fees[feeRedIx].currency));
      DI.setInt("HighClassFeeRed", oe.interpretCurrency(fees[feeRedLateIx].fee, fees[feeRedLateIx].currency));

      FeeStatistics feeStat;
      feeStat.fee = fees[feeIx].fee;
      if (feeStat.fee > 0) {
        feeStat.lateFactor = fees[feeLateIx].fee / feeStat.fee;
        feeStatistics.push_back(feeStat);
      }
    }
  }
  pc->synchronize();

  return pc;
}

void IOF30Interface::setupRelayClasses(const map<int, vector<LegInfo> > &teamClassConfig) {
  for (map<int, vector<LegInfo> >::const_iterator it = teamClassConfig.begin();
       it != teamClassConfig.end(); ++it) {
    int classId = it->first;
    const vector<LegInfo> &legs = it->second;
    if (legs.empty())
      continue;
    if (classId > 0) {
      pClass pc = oe.getClass(classId);
      if (!pc) {
        pc = oe.getClassCreate(classId, L"tmp" + itow(classId));
      }
      setupRelayClass(pc, legs);
    }
  }
}

void IOF30Interface::setupRelayClass(pClass pc, const vector<LegInfo> &legs) {
  if (pc) {
    int nStage = 0;
    for (size_t k = 0; k < legs.size(); k++) {
      nStage += legs[k].maxRunners;
    }
    if (int(pc->getNumStages())>=nStage)
      return; // Do nothing
    
    pc->setNumStages(nStage);
    pc->setStartType(0, STTime, false);
    pc->setStartData(0, oe.getAbsTime(3600));

    int ix = 0;
    for (size_t k = 0; k < legs.size(); k++) {
      for (int j = 0; j < legs[k].maxRunners; j++) {
        if (j>0) {
          if (j < legs[k].minRunners)
            pc->setLegType(ix, LTParallel);
          else
            pc->setLegType(ix, LTExtra);

          pc->setStartType(ix, STChange, false);
        }
        else if (k>0) {
          pc->setLegType(ix, LTNormal);
          pc->setStartType(ix, STChange, false);
        }
        ix++;
      }
    }
  }
}

wstring IOF30Interface::getCurrentTime() const {
  // Don't call this method at midnight!
  return getLocalDate() + L"T" + getLocalTimeOnly();
}

int IOF30Interface::parseISO8601Time(const xmlobject &xo) {
  if (!xo)
    return 0;
  const char *t = xo.getRaw();
  int tIx = -1;
  int zIx = -1;
  for (int k = 0; t[k] != 0; k++) {
    if (t[k] == 'T' || t[k] == 't') {
      if (tIx == -1)
        tIx = k;
      else ;
        // Bad format
    }
    else if (t[k] == '+' || t[k] == '-' || t[k] == 'Z') {
      if (zIx == -1 && tIx != -1)
        zIx = k;
      else ;
        // Bad format
    }
  }
  string date = t;
  string time = tIx >= 0 ? date.substr(tIx+1) : date;
  string zone = (tIx >= 0 && zIx > 0) ? time.substr(zIx - tIx - 1) : "";

  if (tIx > 0) {
    date = date.substr(0, tIx);

    if (zIx > 0)
      time = time.substr(0, zIx - tIx - 1);
  }

  return oe.getRelativeTime(date, time, zone);
}

void IOF30Interface::getProps(vector<wstring> &props) const {
  props.push_back(L"xmlns");
  props.push_back(L"http://www.orienteering.org/datastandard/3.0");

  props.push_back(L"xmlns:xsi");
  props.push_back(L"http://www.w3.org/2001/XMLSchema-instance");

  props.push_back(L"iofVersion");
  props.push_back(L"3.0");

  props.push_back(L"createTime");
  props.push_back(getCurrentTime());

  props.push_back(L"creator");
  props.push_back(L"MeOS " + getMeosCompectVersion());
}

void IOF30Interface::writeResultList(xmlparser &xml, const set<int> &classes,
                                     int leg,  bool useUTC_, 
                                     bool teamsAsIndividual_, bool unrollLoops_,
                                     bool includeStageInfo_) {
  useGMT = useUTC_;
  includeStageRaceInfo = includeStageInfo_;
  teamsAsIndividual = teamsAsIndividual_;
  unrollLoops = unrollLoops_;
  vector<wstring> props;
  getProps(props);

  props.push_back(L"status");
  props.push_back(L"Complete");

  xml.startTag("ResultList", props);

  writeEvent(xml);

  vector<pClass> c;
  oe.getClasses(c, false);

  for (size_t k = 0; k < c.size(); k++) {
//    bool indRel = c[k]->getClassType() == oClassIndividRelay;

    if (classes.empty() || classes.count(c[k]->getId())) {
 /*     oe.getRunners(c[k]->getId(), r, false);
      vector<pRunner> rToUse;
      rToUse.reserve(r.size());

      for (size_t j = 0; j < r.size(); j++) {
        if (leg == -1 || leg == r[j]->getLegNumber()) {
          if (leg == -1 && indRel && r[j]->getLegNumber() != 0)
            continue; // Skip all but leg 0 for individual relay

          if (leg == -1 && !indRel && r[j]->getTeam())
            continue; // For teams, skip presonal results, unless individual relay

          if (r[j]->getStatus() == StatusUnknown)
            continue;

          rToUse.push_back(r[j]);
        }
      }

      vector<pTeam> tToUse;

      if (leg == -1) {
        oe.getTeams(c[k]->getId(), t, false);
        tToUse.reserve(t.size());

        for (size_t j = 0; j < t.size(); j++) {
          for (int n = 0; n < t[j]->getNumRunners(); n++) {
            pRunner tr = t[j]->getRunner(n);
            if (tr && tr->getStatus() != StatusUnknown) {
              tToUse.push_back(t[j]);
              break;
            }
          }
        }

      }
   */
      vector<pRunner> rToUse;
      vector<pTeam> tToUse;
      getRunnersToUse(c[k], rToUse, tToUse, leg, false);

      if (!rToUse.empty() || !tToUse.empty()) {
        writeClassResult(xml, *c[k], rToUse, tToUse);
      }
    }
  }


  xml.endTag();
}

void IOF30Interface::writeClassResult(xmlparser &xml,
                                      const oClass &c,
                                      const vector<pRunner> &r,
                                      const vector<pTeam> &t) {
  pCourse stdCourse = haveSameCourse(r);

  xml.startTag("ClassResult");
  writeClass(xml, c);
  if (stdCourse)
    writeCourse(xml, *stdCourse);

  bool hasInputTime = false;
  for (size_t k = 0; !hasInputTime && k < r.size(); k++) {
    if (r[k]->hasInputData())
      hasInputTime = true;
  }

  for (size_t k = 0; !hasInputTime && k < t.size(); k++) {
    if (t[k]->hasInputData())
      hasInputTime = true;
  }

  for (size_t k = 0; k < r.size(); k++) {
    writePersonResult(xml, *r[k], stdCourse == 0, false, hasInputTime);
  }

  for (size_t k = 0; k < t.size(); k++) {
    writeTeamResult(xml, *t[k], hasInputTime);
  }

  xml.endTag();
}

pCourse IOF30Interface::haveSameCourse(const vector<pRunner> &r) const {
  bool sameCourse = true;
  pCourse stdCourse = r.size() > 0 ? r[0]->getCourse(false) : 0;
  for (size_t k = 1; sameCourse && k < r.size(); k++) {
    int nr = r[k]->getNumMulti();
    for (int j = 0; j <= nr; j++) {
      pRunner tr = r[k]->getMultiRunner(j);
      if (tr && stdCourse != tr->getCourse(true)) {
        sameCourse = false;
        return 0;
      }
    }
  }
  return stdCourse;
}

void IOF30Interface::writeClass(xmlparser &xml, const oClass &c) {
  xml.startTag("Class");
  xml.write("Id", c.getExtIdentifierString()); // Need to call initClassId first
  xml.write("Name", c.getName());

  oClass::ClassStatus stat = c.getClassStatus();
  if (stat == oClass::Invalid)
    xml.write("Status", L"Invalidated");
  else if (stat == oClass::InvalidRefund)
    xml.write("Status", L"InvalidatedNoFee");

  xml.endTag();
}

void IOF30Interface::writeCourse(xmlparser &xml, const oCourse &c) {
  xml.startTag("Course");
  writeCourseInfo(xml, c);
  xml.endTag();
}

void IOF30Interface::writeCourseInfo(xmlparser &xml, const oCourse &c) {
  xml.write("Id", c.getId());
  xml.write("Name", c.getName());
  int len = c.getLength();
  if (len > 0)
    xml.write("Length", len);
  int climb = c.getDCI().getInt("Climb");
  if (climb > 0)
    xml.write("Climb", climb);
}


wstring formatStatus(RunnerStatus st) {
  switch (st) {
    case StatusOK:
      return L"OK";
    case StatusDNS:
      return L"DidNotStart";
    case StatusCANCEL:
      return L"Cancelled";
    case StatusMP:
      return L"MissingPunch";
    case StatusDNF:
      return L"DidNotFinish";
    case StatusDQ:
      return L"Disqualified";
    case StatusMAX:
      return L"OverTime";
    case StatusNotCompetiting:
      return L"NotCompeting";
    default:
      return L"Inactive";
  }
}

void IOF30Interface::writePersonResult(xmlparser &xml, const oRunner &r,
                                       bool includeCourse, bool teamMember, bool hasInputTime) {
  if (!teamMember)
    xml.startTag("PersonResult");
  else
    xml.startTag("TeamMemberResult");

  writePerson(xml, r);
  const pClub pc = r.getClubRef();

  if (pc && !r.isVacant())
    writeClub(xml, *pc, false);

  if (teamMember) {
    oRunner const *resultHolder = &r;
    cTeam t = r.getTeam();
    const oClass *cls = r.getClassRef(false);

    if (t && cls) {
      int leg = r.getLegNumber();
      const int legOrg = leg;
      while (cls->getLegType(leg) == LTIgnore && leg > 0) {
        leg--;
      }
      if (leg < legOrg && t->getRunner(leg))
        resultHolder = t->getRunner(leg);
    }

    writeResult(xml, r, *resultHolder, includeCourse, 
                includeStageRaceInfo && (r.getNumMulti() > 0 || r.getRaceNo() > 0), teamMember, hasInputTime);
  }
  else {
    if (r.getNumMulti() > 0) {
      for (int k = 0; k <= r.getNumMulti(); k++) {
        const pRunner tr = r.getMultiRunner(k);
        if (tr)
          writeResult(xml, *tr, *tr, includeCourse, includeStageRaceInfo, teamMember, hasInputTime);
      }
    }
    else
      writeResult(xml, r, r, includeCourse, false, teamMember, hasInputTime);
  }


  xml.endTag();
}

void IOF30Interface::writeResult(xmlparser &xml, const oRunner &rPerson, const oRunner &r,
                                 bool includeCourse, bool includeRaceNumber,
                                 bool teamMember, bool hasInputTime) {

  vector<SplitData> dummy;
  if (!includeRaceNumber && getStageNumber() == 0)
    xml.startTag("Result");
  else {
    int rn = getStageNumber();
    if (rn == 0)
      rn = 1;
    if (includeRaceNumber)
      rn += rPerson.getRaceNo();
    xml.startTag("Result", "raceNumber", itos(rn));
  }

  if (teamMember)
    writeLegOrder(xml, rPerson);

  wstring bib = rPerson.getBib();
  if (!bib.empty())
    xml.write("BibNumber", bib);

  if (r.getStartTime() > 0)
    xml.write("StartTime", oe.getAbsDateTimeISO(r.getStartTime(), true, useGMT));

  if (r.getFinishTime() > 0)
    xml.write("FinishTime", oe.getAbsDateTimeISO(r.getFinishTimeAdjusted(), true, useGMT));

  if (r.getRunningTime() > 0)
    xml.write("Time", r.getRunningTime());

  int after = r.getTimeAfter();
  if (after >= 0) {
    if (teamMember) {
      xml.write("TimeBehind", "type", L"Leg", itow(after));

      after = r.getTimeAfterCourse();
      if (after >= 0)
        xml.write("TimeBehind", "type", L"Course", itow(after));

    }
    else
      xml.write("TimeBehind", after);
  }

  if (r.getClassRef(false)) {

    if (r.statusOK() && r.getClassRef(false)->getNoTiming() == false) {
      if (!teamMember && r.getPlace() > 0 && r.getPlace() < 50000) {
        xml.write("Position", r.getPlace());
      }
      else if (teamMember) {
        //int pos = r.getTeam()->getLegPlace(r.getLegNumber(), false);
        int pos = r.getPlace();
        if (pos > 0 && pos < 50000)
          xml.write("Position", "type", L"Leg", itow(pos));

        pos = r.getCoursePlace();
        if (pos > 0)
          xml.write("Position", "type", L"Course", itow(pos));
      }
    }

    xml.write("Status", formatStatus(r.getStatus()));

    if ( (r.getTeam() && r.getClassRef(false)->getClassType() != oClassPatrol && !teamsAsIndividual) || hasInputTime) {
      xml.startTag("OverallResult");
      int rt = r.getTotalRunningTime();
      if (rt > 0)
        xml.write("Time", rt);

      bool hasTiming = r.getClassRef(false)->getNoTiming() == false;
      RunnerStatus stat = r.getTotalStatus();

      int tleg = r.getLegNumber() >= 0 ? r.getLegNumber() : 0;

      if (stat == StatusOK && hasTiming) {
        int after = r.getTotalRunningTime() - r.getClassRef(true)->getTotalLegLeaderTime(tleg, true);
        if (after >= 0)
          xml.write("TimeBehind", after);
      }

      if (stat == StatusOK && hasTiming)
        xml.write("Position", r.getTotalPlace());

      xml.write("Status", formatStatus(stat));

      xml.endTag();
    }

    pCourse crs = r.getCourse(!unrollLoops);
    if (crs) {
      if (includeCourse)
        writeCourse(xml, *crs);

      const vector<SplitData> &sp = r.getSplitTimes(unrollLoops);
      if (r.getStatus()>0 && r.getStatus() != StatusDNS && 
                             r.getStatus() != StatusCANCEL && 
                             r.getStatus() != StatusNotCompetiting) {
        int nc = crs->getNumControls();
        bool hasRogaining = crs->hasRogaining();
        int firstControl = crs->useFirstAsStart() ? 1 : 0;
        if (crs->useLastAsFinish()) {
          nc--;
        }
        set< pair<unsigned, int> > rogaining;
        for (int k = firstControl; k<nc; k++) {
          if (size_t(k) >= sp.size())
            break;
          if (crs->getControl(k)->isRogaining(hasRogaining)) {
            if (sp[k].hasTime()) {
              int time = sp[k].time - r.getStartTime();
              int control = crs->getControl(k)->getFirstNumber();
              rogaining.insert(make_pair(time, control));
            }
            else if (!sp[k].isMissing()) {
              int control = crs->getControl(k)->getFirstNumber();
              rogaining.insert(make_pair(-1, control));
            }
            continue;
          }

          if (sp[k].isMissing())
            xml.startTag("SplitTime", "status", "Missing");
          else
            xml.startTag("SplitTime");
          xml.write("ControlCode", crs->getControl(k)->getFirstNumber());
          if (sp[k].hasTime())
            xml.write("Time", sp[k].time - r.getStartTime());
          xml.endTag();
        }

        for (set< pair<unsigned, int> >::iterator it = rogaining.begin(); it != rogaining.end(); ++it) {
          xml.startTag("SplitTime", "status", "Additional");
          xml.write("ControlCode", it->second);
          if (it->first != -1)
            xml.write("Time", it->first);
          xml.endTag();
        }
      }
    }
  }

  if (rPerson.getCardNo() > 0)
    xml.write("ControlCard", rPerson.getCardNo());

  writeFees(xml, rPerson);

  xml.endTag();
}

void IOF30Interface::writeFees(xmlparser &xml, const oRunner &r) const {
  int cardFee = r.getDCI().getInt("CardFee");
  bool paidCard = r.getDCI().getInt("Paid") >= cardFee;
  
  writeAssignedFee(xml, r, paidCard ? cardFee : 0);

  if (cardFee > 0) 
    writeRentalCardService(xml, cardFee, paidCard);
}

void IOF30Interface::writeTeamResult(xmlparser &xml, const oTeam &t, bool hasInputTime) {
  xml.startTag("TeamResult");

  xml.write("EntryId", t.getId());
  xml.write("Name", t.getName());

  if (t.getClubRef())
    writeClub(xml, *t.getClubRef(), false);

  wstring bib = t.getBib();
  if (!bib.empty())
    xml.write("BibNumber", bib);

  for (int k = 0; k < t.getNumRunners(); k++) {
    if (t.getRunner(k))
      writePersonResult(xml, *t.getRunner(k), true, true, hasInputTime);
  }

  writeAssignedFee(xml, t, 0);
  xml.endTag();
}

int IOF30Interface::getStageNumber() {
  if (cachedStageNumber >= 0)
    return cachedStageNumber;
  int sn = oe.getStageNumber();
  if (sn != 0) {
    if (sn < 0)
      sn = 0;
    cachedStageNumber = sn;
    return sn;
  }
  bool pre = oe.hasPrevStage();
  bool post = oe.hasNextStage();

  if (!pre && !post) {
    cachedStageNumber = 0;
  }
  else if (!pre && post) {
    cachedStageNumber = 1;
  }
  else {
    cachedStageNumber = 1;
    // Guess from stage name
    wstring name = oe.getName();
    if (!name.empty()) {
      wchar_t d = name[name.length() -1];
      if (d>='1' && d<='9')
        cachedStageNumber = d - '0';
    }
  }

  return cachedStageNumber;
}

void IOF30Interface::writeEvent(xmlparser &xml) {
  xml.startTag("Event");
  xml.write("Id", oe.getExtIdentifierString());
  xml.write("Name", oe.getName());
  xml.startTag("StartTime");
  xml.write("Date", oe.getDate());
  xml.write("Time", oe.getAbsDateTimeISO(0, false, useGMT));
  xml.endTag();

  if (getStageNumber()) {
    xml.startTag("Race");
    xml.write("RaceNumber", getStageNumber());

    xml.write("Name", oe.getName());

    xml.startTag("StartTime");
    xml.write("Date", oe.getDate());
    xml.write("Time", oe.getAbsDateTimeISO(0, false, useGMT));
    xml.endTag();

    xml.endTag();
  }

  xml.endTag();
}

void IOF30Interface::writePerson(xmlparser &xml, const oRunner &r) {
  xml.startTag("Person");

  __int64 id = r.getExtIdentifier();
  if (id != 0)
    xml.write("Id", r.getExtIdentifierString());

  xml.startTag("Name");
  xml.write("Family", r.getFamilyName());
  xml.write("Given", r.getGivenName());
  xml.endTag();

  xml.endTag();
}

void IOF30Interface::writeClub(xmlparser &xml, const oClub &c, bool writeExtended) const {
  if (c.isVacant() || c.getName().empty())
    return;

  if (writeExtended) {
    const wstring &type = c.getDCI().getString("Type");
    if (type.empty())
      xml.startTag("Organisation");
    else
      xml.startTag("Organisation", "type", type);
  }
  else {
    xml.startTag("Organisation");
  }
  __int64 id = c.getExtIdentifier();
  if (id != 0)
    xml.write("Id", c.getExtIdentifierString());

  xml.write("Name", c.getName());
  wstring sname = c.getDCI().getString("ShortName");
  if (!sname.empty())
    xml.write("ShortName", sname);

  wstring ctry = c.getDCI().getString("Country");
  wstring nat = c.getDCI().getString("Nationality");

  if (!ctry.empty() || !nat.empty()) {
    if (ctry.empty()) {
      if (nat == L"SWE")
        ctry = L"Sweden";
      else if (nat == L"FR" || nat == L"FRA")
        ctry = L"France";
      else
        ctry = nat;
    }
    xml.write("Country", "code", nat, ctry);
  }

  if (writeExtended) {
    oDataConstInterface di = c.getDCI();
    const wstring &street = di.getString("Street");
    const wstring &co = di.getString("CareOf");
    const wstring &city = di.getString("City");
    const wstring &state = di.getString("State");
    const wstring &zip = di.getString("ZIP");
    const wstring &email = di.getString("EMail");
    const wstring &phone = di.getString("Phone");

    if (!street.empty()) {
      xml.startTag("Address");
      xml.write("Street", street);
      if (!co.empty())
        xml.write("CareOf", co);
      if (!zip.empty())
        xml.write("ZipCode", zip);
      if (!city.empty())
        xml.write("City", city);
      if (!state.empty())
        xml.write("State", state);
      if (!ctry.empty())
        xml.write("Country", ctry);
      xml.endTag();
    }

    if (!email.empty())
      xml.write("Contact", "type", L"EmailAddress", email);

    if (!phone.empty())
      xml.write("Contact", "type", L"PhoneNumber", phone);

    int dist = di.getInt("District");
    if (dist > 0)
      xml.write("ParentOrganisationId", dist);
  }

  xml.endTag();
}

void IOF30Interface::writeStartList(xmlparser &xml, const set<int> &classes, bool useUTC_, 
                                    bool teamsAsIndividual_, bool includeStageInfo_) {
  useGMT = useUTC_;
  teamsAsIndividual = teamsAsIndividual_;
  includeStageRaceInfo = includeStageInfo_;
  vector<wstring> props;
  getProps(props);

  xml.startTag("StartList", props);

  writeEvent(xml);

  vector<pClass> c;
  oe.getClasses(c, false);

  for (size_t k = 0; k < c.size(); k++) {

    if (classes.empty() || classes.count(c[k]->getId())) {
      vector<pRunner> rToUse;
      vector<pTeam> tToUse;
      getRunnersToUse(c[k], rToUse, tToUse, -1, true);
      if (!rToUse.empty() || !tToUse.empty()) {
        writeClassStartList(xml, *c[k], rToUse, tToUse);
      }
    }
  }
  xml.endTag();
}

void IOF30Interface::getRunnersToUse(const pClass cls, vector<pRunner> &rToUse,
                                     vector<pTeam> &tToUse, int leg, bool includeUnknown) const {

  rToUse.clear();
  tToUse.clear();
  vector<pRunner> r;
  vector<pTeam> t;

  int classId = cls->getId();
  bool indRel = cls->getClassType() == oClassIndividRelay;

  oe.getRunners(classId, 0, r, false);
  rToUse.reserve(r.size());

  for (size_t j = 0; j < r.size(); j++) {
    if (leg == -1 || leg == r[j]->getLegNumber()) {

      if (!teamsAsIndividual) {
        if (leg == -1 && indRel && r[j]->getLegNumber() != 0)
          continue; // Skip all but leg 0 for individual relay

        if (leg == -1 && !indRel && r[j]->getTeam())
          continue; // For teams, skip presonal results, unless individual relay

        if (!includeUnknown && r[j]->getStatus() == StatusUnknown)
          continue;
      }
      rToUse.push_back(r[j]);
    }
  }

  if (leg == -1 && !teamsAsIndividual) {
    oe.getTeams(classId, t, false);
    tToUse.reserve(t.size());

    for (size_t j = 0; j < t.size(); j++) {
      if (includeUnknown)
         tToUse.push_back(t[j]);
      else {
        for (int n = 0; n < t[j]->getNumRunners(); n++) {
          pRunner tr = t[j]->getRunner(n);
          if (tr && tr->getStatus() != StatusUnknown) {
            tToUse.push_back(t[j]);
            break;
          }
        }
      }
    }
  }
}

void IOF30Interface::writeClassStartList(xmlparser &xml, const oClass &c,
                                         const vector<pRunner> &r,
                                         const vector<pTeam> &t) {

  pCourse stdCourse = haveSameCourse(r);

  xml.startTag("ClassStart");
  writeClass(xml, c);
  if (stdCourse)
    writeCourse(xml, *stdCourse);

  wstring start = c.getStart();
  if (!start.empty())
    xml.write("StartName", start);

  for (size_t k = 0; k < r.size(); k++) {
    writePersonStart(xml, *r[k], stdCourse == 0, false);
  }

  for (size_t k = 0; k < t.size(); k++) {
    writeTeamStart(xml, *t[k]);
  }
  xml.endTag();
}

void IOF30Interface::writePersonStart(xmlparser &xml, const oRunner &r, bool includeCourse, bool teamMember) {
  if (!teamMember)
    xml.startTag("PersonStart");
  else
    xml.startTag("TeamMemberStart");

  writePerson(xml, r);
  const pClub pc = r.getClubRef();

  if (pc && !r.isVacant())
    writeClub(xml, *pc, false);

  if (teamMember) {
    writeStart(xml, r, includeCourse, includeStageRaceInfo && (r.getNumMulti() > 0 || r.getRaceNo() > 0), teamMember);
  }
  else {
    if (r.getNumMulti() > 0) {
      for (int k = 0; k <= r.getNumMulti(); k++) {
        const pRunner tr = r.getMultiRunner(k);
        if (tr)
          writeStart(xml, *tr, includeCourse, includeStageRaceInfo, teamMember);
      }
    }
    else
      writeStart(xml, r, includeCourse, false, teamMember);
  }

  xml.endTag();
}

void IOF30Interface::writeTeamStart(xmlparser &xml, const oTeam &t) {
  xml.startTag("TeamStart");

  xml.write("EntryId", t.getId());
  xml.write("Name", t.getName());

  if (t.getClubRef())
    writeClub(xml, *t.getClubRef(), false);

  wstring bib = t.getBib();
  if (!bib.empty())
    xml.write("BibNumber", bib);

  for (int k = 0; k < t.getNumRunners(); k++) {
    if (t.getRunner(k))
      writePersonStart(xml, *t.getRunner(k), true, true);
  }

  writeAssignedFee(xml, t, 0);
  xml.endTag();
}

void IOF30Interface::writeStart(xmlparser &xml, const oRunner &r,
                                bool includeCourse, bool includeRaceNumber,
                                bool teamMember) {
  if (!includeRaceNumber && getStageNumber() == 0)
    xml.startTag("Start");
  else {
    int rn = getStageNumber();
    if (rn == 0)
      rn = 1;
    if (includeStageRaceInfo)
      rn += r.getRaceNo();

    xml.startTag("Start", "raceNumber", itos(rn));
  }
  if (teamMember)
    writeLegOrder(xml, r);

  wstring bib = r.getBib();
  if (!bib.empty())
    xml.write("BibNumber", bib);

  if (r.getStartTime() > 0)
    xml.write("StartTime", oe.getAbsDateTimeISO(r.getStartTime(), true, useGMT));

  pCourse crs = r.getCourse(true);
  if (crs && includeCourse)
    writeCourse(xml, *crs);

  if (r.getCardNo() > 0)
    xml.write("ControlCard", r.getCardNo());

  writeFees(xml, r);

  xml.endTag();
}

void IOF30Interface::writeLegOrder(xmlparser &xml, const oRunner &r) const {
  // Team member race result
  int legNumber, legOrder;
  const oClass *pc = r.getClassRef(false);
  if (pc) {
    bool par = pc->splitLegNumberParallel(r.getLegNumber(), legNumber, legOrder);
    xml.write("Leg", legNumber + 1);
    if (par)
      xml.write("LegOrder", legOrder + 1);
  }
}

bool IOF30Interface::readXMLCompetitorDB(const xmlobject &xCompetitor) {

  if (!xCompetitor) return false;

  xmlobject person = xCompetitor.getObject("Person");

  if (!person) return false;

  wstring pidS;
  person.getObjectString("Id", pidS);
  long long pid = oBase::converExtIdentifierString(pidS);
  xmlobject pname = person.getObject("Name");
  if (!pname) return false;

  int cardno=0;
  string tmp;

  xmlList cards;
  xCompetitor.getObjects("ControlCard", cards);

  for (size_t k= 0; k<cards.size(); k++) {
    xmlobject &card = cards[k];
    if (card) {
      xmlattrib pSystem = card.getAttrib("punchingSystem");
      if (!pSystem || _stricmp(pSystem.get(), "SI") == 0) {
        cardno = card.getObjectInt(0);
        break;
      }
    }
  }

  wstring given;
  pname.getObjectString("Given", given);
  getFirst(given, 2);
  wstring family;
  pname.getObjectString("Family", family);

  if (given.empty() || family.empty())
    return false;

  //string name(given+" "+family);
  wstring name(family + L", " + given);

  char sex[2];
  person.getObjectString("sex", sex, 2);

  int birth = person.getObjectInt("BirthDate");

  xmlobject nat=person.getObject("Nationality");

  char national[4]={0,0,0,0};
  if (nat) {
    nat.getObjectString("code", national, 4);
  }

  int clubId = 0;
  xmlobject xClub = xCompetitor.getObject("Organisation");
  if (xClub) {
    clubId = xClub.getObjectInt("Id");
  }

  RunnerDB &runnerDB = oe.getRunnerDatabase();

  RunnerWDBEntry *rde = runnerDB.getRunnerById(pid);

  if (!rde) {
    rde = runnerDB.getRunnerByCard(cardno);

    if (rde && rde->getExtId()!=0)
      rde = 0; //Other runner, same card

    if (!rde)
      rde = runnerDB.addRunner(name.c_str(), pid, clubId, cardno);
  }

  if (rde) {
    rde->setExtId(pid);
    rde->setName(name.c_str());
    rde->dbe().clubNo = clubId;
    rde->dbe().birthYear = extendYear(birth);
    rde->dbe().sex = sex[0];
    memcpy(rde->dbe().national, national, 3);
  }
  return true;
}

void IOF30Interface::writeXMLCompetitorDB(xmlparser &xml, const RunnerWDBEntry &rde) const {
  wstring s = rde.getSex();

  xml.startTag("Competitor");

  if (s.empty())
    xml.startTag("Person");
  else
    xml.startTag("Person", "sex", s);

  long long pid = rde.getExtId();
  if (pid > 0) {
    wchar_t bf[16];
    oBase::converExtIdentifierString(pid, bf);
    xml.write("Id", bf);
  }
  xml.startTag("Name");
  xml.write("Given", rde.getGivenName());
  xml.write("Family", rde.getFamilyName());
  xml.endTag();

  if (rde.getBirthYear() > 1900)
    xml.write("BirthDate", itow(rde.getBirthYear()) + L"-01-01");

  wstring nat = rde.getNationality();
  if (!nat.empty()) {
    xml.write("Nationality", "code", nat.c_str());
  }

  xml.endTag(); // Person

  if (rde.dbe().cardNo > 0) {
    xml.write("ControlCard", "punchingSystem", L"SI", itow(rde.dbe().cardNo));
  }


  if (rde.dbe().clubNo > 0) {
    xml.startTag("Organisation");
    xml.write("Id", rde.dbe().clubNo);
    xml.endTag();
  }

  xml.endTag(); // Competitor
}

int getStartIndex(int sn);
int getFinishIndex(int sn);
wstring getStartName(const wstring &start);

int IOF30Interface::getStartIndex(const wstring &startId) {
  int num = getNumberSuffix(startId);
  if (num == 0 && startId.length()>0)
    num = int(startId[startId.length()-1])-'0';
  return ::getStartIndex(num);
}

bool IOF30Interface::readControl(const xmlobject &xControl) {
  if (!xControl)
    return false;

  wstring idStr;
  xControl.getObjectString("Id", idStr);

  if (idStr.empty())
    return false;

  int type = -1;
  int code = 0;
  if (idStr[0] == 'S')
    type = 1;
  else if (idStr[0] == 'F')
    type = 2;
  else {
    type = 0;
    code = _wtoi(idStr.c_str());
    if (code <= 0)
      return false;
  }

  xmlobject pos = xControl.getObject("MapPosition");

  int xp = 0, yp = 0;
  if (pos) {
    string x,y;
    pos.getObjectString("x", x);
    pos.getObjectString("y", y);
    xp = int(10.0 * atof(x.c_str()));
    yp = int(10.0 * atof(y.c_str()));
  }

  int longitude = 0, latitude = 0;

  xmlobject geopos = xControl.getObject("Position");

  if (geopos) {
    string lat,lng;
    geopos.getObjectString("lat", lat);
    geopos.getObjectString("lng", lng);
    latitude = int(1e6 * atof(lat.c_str()));
    longitude = int(1e6 * atof(lng.c_str()));
  }
  pControl pc = 0;

  if (type == 0) {
    pc = oe.getControl(code, true);
  }
  else if (type == 1) {
    wstring start = getStartName(trim(idStr));
    pc = oe.getControl(getStartIndex(idStr), true);
    pc->setNumbers(L"");
    pc->setName(start);
    pc->setStatus(oControl::StatusStart);
  }
  else if (type == 2) {
    wstring finish = trim(idStr);
    int num = getNumberSuffix(finish);
    if (num == 0 && finish.length()>0)
      num = int(finish[finish.length()-1])-'0';
    if (num > 0 && num<10)
      finish = lang.tl(L"Mål ") + itow(num);
    else
      finish = lang.tl(L"Mål");
    pc = oe.getControl(getFinishIndex(num), true);
    pc->setNumbers(L"");
    pc->setName(finish);
    pc->setStatus(oControl::StatusFinish);
  }

  if (pc) {
    pc->getDI().setInt("xpos", xp);
    pc->getDI().setInt("ypos", yp);
    pc->getDI().setInt("longcrd", longitude);
    pc->getDI().setInt("latcrd", latitude);
    pc->synchronize();
  }
  return true;
}

void IOF30Interface::readCourseGroups(xmlobject xClassCourse, vector< vector<pCourse> > &crs) {
  xmlList groups;
  xClassCourse.getObjects("CourseGroup", groups);

  for (size_t k = 0; k < groups.size(); k++) {
    xmlList courses;
    groups[k].getObjects("Course", courses);
    crs.push_back(vector<pCourse>());
    for (size_t j = 0; j < courses.size(); j++) {
      pCourse pc = readCourse(courses[j]);
      if (pc)
        crs.back().push_back(pc);
    }
  }
}

wstring IOF30Interface::constructCourseName(const wstring &family, const wstring &name) {
  if (family.empty())
    return trim(name);
  else
    return trim(family) + L":" + trim(name);
}

wstring IOF30Interface::constructCourseName(const xmlobject &xcrs) {
  wstring name, family;
  xcrs.getObjectString("Name", name);
  if (name.empty())
    // CourseAssignment case
    xcrs.getObjectString("CourseName", name);

  xcrs.getObjectString("CourseFamily", family);

  return constructCourseName(family, name);
}

pCourse IOF30Interface::readCourse(const xmlobject &xcrs) {
  if (!xcrs)
    return 0;

  int cid = xcrs.getObjectInt("Id");

  wstring name = constructCourseName(xcrs);
  /*, family;
  xcrs.getObjectString("Name", name);
  xcrs.getObjectString("CourseFamily", family);

  if (family.empty())
    name = trim(name);
  else
    name = trim(family) + ":" + trim(name);
*/
  int len = xcrs.getObjectInt("Length");
  int climb = xcrs.getObjectInt("Climb");

  xmlList xControls;
  xcrs.getObjects("CourseControl", xControls);

  vector<pControl> ctrlCode;
  vector<int> legLen;
  wstring startName;
  bool hasRogaining = false;

  for (size_t k = 0; k < xControls.size(); k++) {
    string type;
    xControls[k].getObjectString("type", type);
    if (type == "Start") {
      wstring idStr;
      xControls[k].getObjectString("Control", idStr);
      pControl pStart = oe.getControl(getStartIndex(idStr), false);
      if (pStart)
        startName = pStart->getName();
    }
    else if (type == "Finish") {
      legLen.push_back(xControls[k].getObjectInt("LegLength"));
    }
    else {
      xmlList xPunchControls;
      xControls[k].getObjects("Control", xPunchControls);
      pControl pCtrl = 0;
      if (xPunchControls.size() == 1) {
        pCtrl = oe.getControl(xPunchControls[0].getInt(), true);
      }
      else if (xPunchControls.size()>1) {
        pCtrl = oe.addControl(1000*cid + xPunchControls[0].getInt(),xPunchControls[0].getInt(), L"");
        if (pCtrl) {
          wstring cc;
          for (size_t j = 0; j < xPunchControls.size(); j++)
            cc += wstring(xPunchControls[j].getw()) + L" ";

          pCtrl->setNumbers(cc);
        }
      }

      if (pCtrl) {
        legLen.push_back(xControls[k].getObjectInt("LegLength"));
        ctrlCode.push_back(pCtrl);
        int score = xControls[k].getObjectInt("Score");
        if (score > 0) {
          pCtrl->getDI().setInt("Rogaining", score);
          pCtrl->setStatus(oControl::StatusRogaining);
          hasRogaining = true;
        }
      }
    }
  }

  pCourse pc = 0;
  if (cid > 0)
    pc = oe.getCourseCreate(cid);
  else {
    pc = oe.getCourse(name);
    if (pc == 0)
      pc = oe.addCourse(name);
  }

  if (pc) {
    pc->setName(name);
    pc->setLength(len);
    pc->importControls("", false);
    for (size_t i = 0; i<ctrlCode.size(); i++) {
      pc->addControl(ctrlCode[i]->getId());
    }
    if (pc->getNumControls() + 1 == legLen.size())
      pc->setLegLengths(legLen);
    pc->getDI().setInt("Climb", climb);

    pc->setStart(startName, true);
    if (hasRogaining) {
      int mt = oe.getMaximalTime();
      if (mt == 0)
        mt = 3600;
      pc->setMaximumRogainingTime(mt);
    }

    pc->synchronize();
  }
  return pc;
}


void IOF30Interface::bindClassCourse(oClass &pc, const vector< vector<pCourse> > &crs) {
  if (crs.empty())
    return;
  if (crs.size() == 1 && crs[0].size() == 0)
    pc.setCourse(crs[0][0]);
  else {
    unsigned ns = pc.getNumStages();
    ns = max(ns, crs.size());
    pc.setNumStages(ns);
    for (size_t k = 0; k < crs.size(); k++) {
      pc.clearStageCourses(k);
      for (size_t j = 0; j < crs[k].size(); j++) {
        pc.addStageCourse(k, crs[k][j]->getId());
      }
    }
  }
}


void IOF30Interface::writeCourses(xmlparser &xml) {
  vector<wstring> props;
  getProps(props);
  xml.startTag("CourseData", props);

  writeEvent(xml);

  vector<pControl> ctrl;
  vector<pCourse> crs;
  oe.getControls(ctrl, false);

  xml.startTag("RaceCourseData");
  map<int, wstring> ctrlId2ExportId;

  // Start
  xml.startTag("Control");
  xml.write("Id", L"S");
  xml.endTag();
  set<wstring> ids;
  for (size_t k = 0; k < ctrl.size(); k++) {
    if (ctrl[k]->getStatus() != oControl::StatusFinish && ctrl[k]->getStatus() != oControl::StatusStart) {
      wstring id = writeControl(xml, *ctrl[k], ids);
      ctrlId2ExportId[ctrl[k]->getId()] = id;
    }
  }

  // Finish
  xml.startTag("Control");
  xml.write("Id", L"F");
  xml.endTag();

  oe.getCourses(crs);
  for (size_t k = 0; k < crs.size(); k++) {
    writeFullCourse(xml, *crs[k], ctrlId2ExportId);
  }

  xml.endTag();

  xml.endTag();
}

wstring IOF30Interface::writeControl(xmlparser &xml, const oControl &c, set<wstring> &writtenId) {
  int id = c.getFirstNumber();
  wstring ids = itow(id);
  if (writtenId.count(ids) == 0) {
    xml.startTag("Control");
    xml.write("Id", ids);
/*
    <!-- the position of the control given in latitude and longitude -->

<!-- coordinates west of the Greenwich meridian and south of the equator are expressed by negative numbers -->
 <Position lng="17.687623" lat="59.760069"/>
<!-- the position of the control on the printed map, relative to the map's lower left corner -->
 <MapPosition y="58" x="187" unit="mm"/>
 */

    xml.endTag();
    writtenId.insert(ids);
  }

  return ids;

}

void IOF30Interface::writeFullCourse(xmlparser &xml, const oCourse &c,
                                       const map<int, wstring> &ctrlId2ExportId) {

  xml.startTag("Course");
  writeCourseInfo(xml, c);

  xml.startTag("CourseControl", "type", "Start");
  xml.write("Control", L"S");
  xml.endTag();

  for (int i = 0; i < c.getNumControls(); i++) {
    int id = c.getControl(i)->getId();
    xml.startTag("CourseControl", "type", "Control");
    if (ctrlId2ExportId.count(id))
      xml.write("Control", ctrlId2ExportId.find(id)->second);
    else
      throw exception();
    xml.endTag();
  }

  xml.startTag("CourseControl", "type", L"Finish");
  xml.write("Control", L"F");
  xml.endTag();

  xml.endTag();
}

void IOF30Interface::writeRunnerDB(const RunnerDB &db, xmlparser &xml) const {
  vector<wstring> props;
  getProps(props);

  xml.startTag("CompetitorList", props);

  const vector<RunnerWDBEntry> &rdb = db.getRunnerDB();
  for (size_t k = 0; k < rdb.size(); k++) {
    if (!rdb[k].isRemoved())
      writeXMLCompetitorDB(xml, rdb[k]);
  }

  xml.endTag();
}

void IOF30Interface::writeClubDB(const RunnerDB &db, xmlparser &xml) const {
  vector<wstring> props;
  getProps(props);

  xml.startTag("OrganisationList", props);

  const vector<oDBClubEntry> &cdb = db.getClubDB(true);
  for (size_t k = 0; k < cdb.size(); k++) {
    if (!cdb[k].isRemoved())
      writeClub(xml, cdb[k], true);
  }

  xml.endTag();
}

