/************************************************************************
    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 "RunnerDB.h"
#include "xmlparser.h"
#include "oRunner.h"
#include "Table.h"

#include "io.h"
#include "fcntl.h"
#include "sys/stat.h"
#include "meos_util.h"
#include "oDataContainer.h"
#include "meosException.h"

#include <algorithm>
#include <cassert>
#include "intkeymapimpl.hpp"

#include "oEvent.h"

extern gdioutput *gdi_main;


RunnerDB::RunnerDB(oEvent *oe_): oe(oe_)
{
  loadedFromServer = false;
  dataDate = 20100201;
  dataTime = 222222;
  runnerTable = 0;
  clubTable = 0;
}

RunnerDB::~RunnerDB(void)
{
  releaseTables();
}

RunnerDBEntry::RunnerDBEntry()
{
  memset(this, 0, sizeof(RunnerDBEntry));
}

RunnerWDBEntry::RunnerWDBEntry()
{
  name[0] = 0;
}

void RunnerWDBEntry::initName() const {
  if (name[0] == 0) {
    memset(name, 0, sizeof(wchar_t) * baseNameLength);
    const RunnerDBEntry &db = dbe();
    if (db.isUTF()) {
      int len = strlen(db.name);
      len = min(len+1, baseNameLengthUTF-1);
      int wlen = MultiByteToWideChar(CP_UTF8, 0, db.name, len, name, baseNameLength);
      if (wlen == 0)
        wlen = baseNameLength;
      name[wlen-1] = 0;
    }
    else {
      const wstring &wn = gdi_main->recodeToWide(db.name);
      wcsncpy_s(name, wn.c_str(), baseNameLength-1);
    }
  }
}

void RunnerWDBEntry::recode(const RunnerDBEntry &dest) const {
  initName();
  RunnerDBEntry &d = const_cast<RunnerDBEntry &>(dest);
  const int blen = min<int>(wcslen(name)+1, baseNameLength);
  int res = WideCharToMultiByte(CP_UTF8, 0, name, blen, d.name, baseNameLengthUTF-1, 0, 0);
  assert(res < baseNameLengthUTF);
  if (res == 0 && blen > 0)
    res = baseNameLengthUTF-1;
  d.name[res] = 0;
  d.setUTF();
}

RunnerDBEntryV1::RunnerDBEntryV1()
{
  memset(this, 0, sizeof(RunnerDBEntryV1));
}

RunnerDBEntryV2::RunnerDBEntryV2()
{
  memset(this, 0, sizeof(RunnerDBEntryV2));
}

void RunnerWDBEntry::getName(wstring &n) const
{
  initName();
  n=name;
}

void RunnerWDBEntry::setName(const wchar_t *n)
{
  const int blen = min<int>(wcslen(n)+1, baseNameLength);
  memset(name, 0, sizeof(wchar_t) * baseNameLength);
  memcpy(name, n, sizeof(wchar_t) * blen);
  name[baseNameLength-1]=0;

  RunnerDBEntry &d = dbe();
  int res = WideCharToMultiByte(CP_UTF8, 0, name, blen, d.name, baseNameLengthUTF-1, 0, 0);
  d.setUTF();
  assert(res < baseNameLengthUTF);
  if (res == 0 && blen > 0) {
    res = baseNameLengthUTF-1;
    d.name[res] = 0;
    name[0] = 0;
    initName();
  }
  d.name[res] = 0;
}

void RunnerWDBEntry::setNameUTF(const char *n)
{
  const int blen = min<int>(strlen(n)+1, baseNameLength);
  RunnerDBEntry &d = dbe();
  memcpy(d.name, n, blen);
  d.name[baseNameLength-1]=0;
  d.setUTF();
  name[0] = 0;
}

string RunnerDBEntry::getNationality() const
{
  if (national[0] < 30)
    return _EmptyString;

  string n("   ");
  n[0] = national[0];
  n[1] = national[1];
  n[2] = national[2];
  return n;
}

string RunnerDBEntry::getSex() const
{
  if (sex == 0)
    return _EmptyString;
  string n("W");
  n[0] = sex;
  return n;
}


wstring RunnerWDBEntry::getNationality() const
{
  const RunnerDBEntry &d = dbe();
  if (d.national[0] < 30)
    return _EmptyWString;

  wstring n(L"   ");
  n[0] = d.national[0];
  n[1] = d.national[1];
  n[2] = d.national[2];
  return n;
}

wstring RunnerWDBEntry::getSex() const
{
  if (dbe().sex == 0)
    return _EmptyWString;
  wstring n(L"W");
  n[0] = dbe().sex;
  return n;
}

wstring RunnerWDBEntry::getGivenName() const
{
  initName();
  return ::getGivenName(name);
}

wstring RunnerWDBEntry::getFamilyName() const
{
  initName();
  return ::getFamilyName(name);
}

__int64 RunnerWDBEntry::getExtId() const
{
  return dbe().extId;
}

void RunnerWDBEntry::setExtId(__int64 id)
{
  dbe().extId = id;
}

void RunnerDBEntryV2::init(const RunnerDBEntryV1 &dbe)
{
  memcpy(this, &dbe, sizeof(RunnerDBEntryV1));
  extId = 0;
}

void RunnerDBEntry::init(const RunnerDBEntryV2 &dbe)
{
  memcpy(name, dbe.name, 32);

  cardNo = dbe.cardNo;
  clubNo = dbe.clubNo;
  national[0] = dbe.national[0];
  national[1] = dbe.national[1];
  national[2] = dbe.national[2];
  sex = dbe.sex;
  birthYear = dbe.birthYear;
  reserved = dbe.reserved;
  extId = dbe.extId;
}


void RunnerWDBEntry::init(RunnerDB *p, size_t ixin) {
  owner = p;
  ix = ixin;
}
  
RunnerWDBEntry *RunnerDB::addRunner(const wchar_t *name,
                                    __int64 extId,
                                    int club, int card)
{
  assert(rdb.size() == rwdb.size());
  rdb.push_back(RunnerDBEntry());
  rwdb.push_back(RunnerWDBEntry());
  rwdb.back().init(this, rdb.size()-1);

  RunnerWDBEntry &e=rwdb.back();
  RunnerDBEntry &en=rdb.back();
  
  en.cardNo = card;
  en.clubNo = club;
  e.setName(name);
  en.extId = extId;

  if (!check(en) ) {
    rdb.pop_back();
    rwdb.pop_back();
    return 0;
  } else {
    if (card>0)
      rhash[card]=rdb.size()-1;
    if (!idhash.empty())
      idhash[extId] = rdb.size()-1;
    if (!nhash.empty())
      nhash.insert(pair<wstring, int>(canonizeName(e.name), rdb.size()-1));
  }
  return &e;
}


RunnerWDBEntry *RunnerDB::addRunner(const char *nameUTF,
                                    __int64 extId,
                                    int club, int card)
{
  assert(rdb.size() == rwdb.size());
  rdb.push_back(RunnerDBEntry());
  rwdb.push_back(RunnerWDBEntry());
  rwdb.back().init(this, rdb.size()-1);

  RunnerWDBEntry &e=rwdb.back();
  RunnerDBEntry &en=rdb.back();
  
  en.cardNo = card;
  en.clubNo = club;
  e.setNameUTF(nameUTF);
  en.extId = extId;

  if (!check(en) ) {
    rdb.pop_back();
    rwdb.pop_back();
    return 0;
  } else {
    if (card>0)
      rhash[card]=rdb.size()-1;
    if (!idhash.empty())
      idhash[extId] = rdb.size()-1;
    if (!nhash.empty()) {
      wstring wn;
      e.getName(wn);
      nhash.insert(pair<wstring, int>(canonizeName(wn.c_str()), rdb.size()-1));
    }
  }
  return &e;
}

int RunnerDB::addClub(oClub &c, bool createNewId) {
  if (createNewId) {
    oDBClubEntry ce(c, cdb.size(), this);
    cdb.push_back(ce);
    int b = 0;
    while(++b<0xFFFF) {
      int off = (rand() & 0xFFFF);
      int newId = 10000 + off;
      int dummy;
      if (!chash.lookup(newId, dummy)) {
        cdb.back().Id = newId;
        chash[c.getId()]=cdb.size()-1;
        return newId;
      }
    }
    cdb.pop_back();
    throw meosException("Internal database error");
  }

  int value;
  if (!chash.lookup(c.getId(), value)) {
    oDBClubEntry ce(c, cdb.size(), this);
    cdb.push_back(ce);
    chash[c.getId()]=cdb.size()-1;
  }
  else {
    oDBClubEntry ce(c, value, this);
    cdb[value] = ce;
  }
  return c.getId();
}

void RunnerDB::importClub(oClub &club, bool matchName)
{
  pClub pc = getClub(club.getId());

  if (pc && !pc->sameClub(club)) {
    //The new id is used by some other club.
    //Remap old club first

    int oldId = pc->getId();
    int newId = chash.size() + 1;//chash.rbegin()->first + 1;

    for (size_t k=0; k<rdb.size(); k++)
      if (rdb[k].clubNo == oldId)
        rdb[k].clubNo = newId;

    chash[newId] = chash[oldId];
    pc = 0;
  }

  if (pc == 0 && matchName) {
    // Try to find the club under other id
    pc = getClub(club.getName());

    if ( pc!=0 ) {
      // If found under different id, remap to new id
      int oldId = pc->getId();
      int newId = club.getId();

      for (size_t k=0; k<rdb.size(); k++)
        if (rdb[k].clubNo == oldId)
          rdb[k].clubNo = newId;

      pClub base = &cdb[0];
      int index = (size_t(pc) - size_t(base)) / sizeof(oDBClubEntry);
      chash[newId] = index;
    }
  }

  if ( pc )
    *pc = club; // Update old club
  else {
    // Completely new club
    chash [club.getId()] = cdb.size();
    cdb.push_back(oDBClubEntry(club, cdb.size(), this));
  }
}

void RunnerDB::compactifyClubs()
{
  chash.clear();
  freeCIx = 0;

  map<int, int> clubmap;
  for (size_t k=0;k<cdb.size();k++)
    clubmap[cdb[k].getId()] = cdb[k].getId();

  for (size_t k=0;k<cdb.size();k++) {
    oDBClubEntry &ref = cdb[k];
    vector<int> compacted;
    for (size_t j=k+1;j<cdb.size(); j++) {
      if (_wcsicmp(ref.getName().c_str(),
              cdb[j].getName().c_str())==0)
        compacted.push_back(j);
    }

    if (!compacted.empty()) {
      int ba=ref.getDI().getDataAmountMeasure();
      oDBClubEntry *best=&ref;
      for (size_t j=0;j<compacted.size();j++) {
        int nba=cdb[compacted[j]].getDI().getDataAmountMeasure();
        if (nba>ba) {
          best = &cdb[compacted[j]];
          ba=nba;
        }
      }
      swap(ref, *best);

      //Update map
      for (size_t j=0;j<compacted.size();j++) {
        clubmap[cdb[compacted[j]].getId()] = ref.getId();
      }
    }
  }
}

RunnerWDBEntry *RunnerDB::getRunnerByCard(int card) const
{
  if (card == 0)
    return 0;

  int value;
  if (rhash.lookup(card, value))
    return (RunnerWDBEntry *)&rwdb[value];

  return 0;
}

RunnerWDBEntry *RunnerDB::getRunnerByIndex(size_t index) const {
  if (index >= rdb.size())
    throw meosException("Index out of bounds");

  return (RunnerWDBEntry *)&rwdb[index];
}


RunnerWDBEntry *RunnerDB::getRunnerById(__int64 extId) const
{
  if (extId == 0)
    return 0;

  setupIdHash();

  int value;

  if (idhash.lookup(extId, value))
    return (RunnerWDBEntry *)&rwdb[value];

  return 0;
}

RunnerWDBEntry *RunnerDB::getRunnerByName(const wstring &name, int clubId,
                                          int expectedBirthYear) const
{
  if (expectedBirthYear>0 && expectedBirthYear<100)
    expectedBirthYear = extendYear(expectedBirthYear);

  setupNameHash();
  vector<int> ix;
  wstring cname(canonizeName(name.c_str()));
  multimap<wstring, int>::const_iterator it = nhash.find(cname);

  while (it != nhash.end() && cname == it->first) {
    ix.push_back(it->second);
    ++it;
  }

  if (ix.empty())
    return 0;

  if (clubId == 0) {
    if (ix.size() == 1)
      return (RunnerWDBEntry *)&rwdb[ix[0]];
    else
      return 0; // Not uniquely defined.
  }

  // Filter on club
  vector<int> ix2;
  for (size_t k = 0;k<ix.size(); k++)
    if (rdb[ix[k]].clubNo == clubId || rdb[ix[k]].clubNo==0)
      ix2.push_back(ix[k]);

  if (ix2.empty())
    return 0;
  else if (ix2.size() == 1)
    return (RunnerWDBEntry *)&rwdb[ix2[0]];
  else if (expectedBirthYear > 0) {
    int bestMatch = 0;
    int bestYear = 0;
    for (size_t k = 0;k<ix2.size(); k++) {
      const RunnerWDBEntry &re = rwdb[ix2[k]];
      if (abs(re.dbe().birthYear-expectedBirthYear) < abs(bestYear-expectedBirthYear)) {
        bestMatch = ix2[k];
        bestYear = re.dbe().birthYear;
      }
    }
    if (bestYear>0)
      return (RunnerWDBEntry *)&rwdb[bestMatch];
  }

  return 0;
}

void RunnerDB::setupIdHash() const
{
  if (!idhash.empty())
    return;

  for (size_t k=0; k<rdb.size(); k++) {
    if (!rdb[k].isRemoved())
      idhash[rdb[k].extId] = int(k);
  }
}

void RunnerDB::setupNameHash() const
{
  if (!nhash.empty())
    return;

  for (size_t k=0; k<rdb.size(); k++) {
    if (!rdb[k].isRemoved()) {
      rwdb[k].initName();
      nhash.insert(pair<wstring, int>(canonizeName(rwdb[k].name), k));
    }
  }
}

void RunnerDB::setupCNHash() const
{
  if (!cnhash.empty())
    return;

  vector<wstring> split;
  for (size_t k=0; k<cdb.size(); k++) {
    if (cdb[k].isRemoved())
      continue;
    canonizeSplitName(cdb[k].getName(), split);
    for (size_t j = 0; j<split.size(); j++)
      cnhash.insert(pair<wstring, int>(split[j], k));
  }
}

static bool isVowel(int c) {
  return c=='a' || c=='e' || c=='i' ||
         c=='o' || c=='u' || c=='y' ||
         c=='å' || c=='ä' || c=='ö';
}

void RunnerDB::canonizeSplitName(const wstring &name, vector<wstring> &split)
{
  split.clear();
  const wchar_t *cname = name.c_str();
  int k = 0;
  for (k=0; cname[k]; k++)
    if (cname[k] != ' ')
      break;

  wchar_t out[128];
  int outp;
  while (cname[k]) {
    outp = 0;
    while(cname[k] != ' ' && cname[k] && outp<(sizeof(out)-1) ) {
      if (cname[k] == '-') {
        k++;
        break;
      }
      out[outp++] = toLowerStripped(cname[k]);
      k++;
    }
    out[outp] = 0;
    if (outp > 0) {
      for (int j=1; j<outp-1; j++) {
        if (out[j] == 'h') { // Idenity Thor and Tohr
          bool v1 = isVowel(out[j+1]);
          bool v2 = isVowel(out[j-1]);
          if (v1 && !v2)
            out[j] = out[j+1];
          else if (!v1 && v2)
            out[j] = out[j-1];
        }
      }

      if (outp>4 && out[outp-1]=='s')
        out[outp-1] = 0; // Identify Linköping och Linköpings
      split.push_back(out);
    }
    while(cname[k] == ' ')
      k++;
  }
}

bool RunnerDB::getClub(int clubId, wstring &club) const
{
  //map<int,int>::const_iterator it = chash.find(clubId);

  int value;
  if (chash.lookup(clubId, value)) {
  //if (it!=chash.end()) {
  //  int i=it->second;
    club=cdb[value].getName();
    return true;
  }
  return false;
}

oClub *RunnerDB::getClub(int clubId) const
{
  //map<int,int>::const_iterator it = chash.find(clubId);

  //if (it!=chash.end())
  int value;
  if (chash.lookup(clubId, value))
    return pClub(&cdb[value]);

  return 0;
}

oClub *RunnerDB::getClub(const wstring &name) const
{
  setupCNHash();
  vector<wstring> names;
  canonizeSplitName(name, names);
  vector< vector<int> > ix(names.size());
  set<int> iset;

  for (size_t k = 0; k<names.size(); k++) {
    multimap<wstring, int>::const_iterator it = cnhash.find(names[k]);

    while (it != cnhash.end() && names[k] == it->first) {
      ix[k].push_back(it->second);
      ++it;
    }

    if (ix[k].size() == 1 && names[k].length()>3)
      return pClub(&cdb[ix[k][0]]);

    if (iset.empty())
      iset.insert(ix[k].begin(), ix[k].end());
    else {
      set<int> im;
      for (size_t j = 0; j<ix[k].size(); j++) {
        if (iset.count(ix[k][j])==1)
          im.insert(ix[k][j]);
      }
      if (im.size() == 1) {
        int i = *im.begin();
        return pClub(&cdb[i]);
      }
      else if (!im.empty())
        swap(iset, im);
    }
  }

  // Exact compare
  for (set<int>::iterator it = iset.begin(); it != iset.end(); ++it) {
    pClub pc = pClub(&cdb[*it]);
    if (_wcsicmp(pc->getName().c_str(), name.c_str())==0)
      return pc;
  }

  wstring cname = canonizeName(name.c_str());
  // Looser compare
  for (set<int>::iterator it = iset.begin(); it != iset.end(); ++it) {
    pClub pc = pClub(&cdb[*it]);
    if (wcscmp(canonizeName(pc->getName().c_str()), cname.c_str()) == 0 )
      return pc;
  }

  double best = 1;
  double secondBest = 1;
  int bestIndex = -1;
  for (set<int>::iterator it = iset.begin(); it != iset.end(); ++it) {
    pClub pc = pClub(&cdb[*it]);

    double d = stringDistance(cname.c_str(), canonizeName(pc->getName().c_str()));

    if (d<best) {
      bestIndex = *it;
      secondBest = best;
      best = d;
    }
    else if (d<secondBest) {
      secondBest = d;
      if (d<=0.4)
        return 0; // Two possible clubs are too close. We cannot choose.
    }
  }

  if (best < 0.2 && secondBest>0.4)
    return pClub(&cdb[bestIndex]);

  return 0;
}

void RunnerDB::saveClubs(const wstring &file)
{
  xmlparser xml;

  xml.openOutputT(file.c_str(), true, "meosclubs");

  vector<oDBClubEntry>::iterator it;

  xml.startTag("ClubList");

  for (it=cdb.begin(); it != cdb.end(); ++it)
    it->write(xml);

  xml.endTag();

  xml.closeOut();
}

string RunnerDB::getDataDate() const
{
  char bf[128];
  if (dataTime<=0 && dataDate>0)
    sprintf_s(bf, "%04d-%02d-%02d", dataDate/10000,
                                    (dataDate/100)%100,
                                    dataDate%100);
  else if (dataDate>0)
    sprintf_s(bf, "%04d-%02d-%02d %02d:%02d:%02d", dataDate/10000,
                                                 (dataDate/100)%100,
                                                 dataDate%100,
                                                 (dataTime/3600)%24,
                                                 (dataTime/60)%60,
                                                 (dataTime)%60);
  else
    return "2011-01-01 00:00:00";

  return bf;
}

void RunnerDB::setDataDate(const string &date)
{
   int d = convertDateYMS(date.substr(0, 10), false);
   int t = date.length()>11 ? convertAbsoluteTimeHMS(date.substr(11), -1) : 0;

   if (d<=0)
     throw std::exception("Felaktigt datumformat");

   dataDate = d;
   if (t>0)
     dataTime = t;
   else
     dataTime = 0;
}

void RunnerDB::saveRunners(const wstring &file)
{
  int f=-1;
  _wsopen_s(&f, file.c_str(), _O_BINARY|_O_CREAT|_O_TRUNC|_O_WRONLY,
            _SH_DENYWR, _S_IREAD|_S_IWRITE);

  if (f!=-1) {
    int version = 5460003;
    _write(f, &version, 4);
    _write(f, &dataDate, 4);
    _write(f, &dataTime, 4);
    if (!rdb.empty())
      _write(f, &rdb[0], rdb.size()*sizeof(RunnerDBEntry));
    _close(f);
  }
  else throw std::exception("Could not save runner database.");
}

void RunnerDB::loadClubs(const wstring &file)
{
  xmlparser xml;

  xml.read(file);

  xmlobject xo;

  //Get clubs
  xo=xml.getObject("ClubList");
  if (xo) {
    clearClubs();
    loadedFromServer = false;

    xmlList xl;
    xo.getObjects(xl);

    xmlList::const_iterator it;
    cdb.clear();
    chash.clear();
    freeCIx = 0;
    cdb.reserve(xl.size());
    for (it=xl.begin(); it != xl.end(); ++it) {
      if (it->is("Club")){
        oDBClubEntry c(oe, cdb.size(), this);
        c.set(*it);
        int value;
        if (c.getId() == 0)
          continue;
        //if (chash.find(c.getId()) == chash.end()) {
        if (!chash.lookup(c.getId(), value)) {
          chash[c.getId()]=cdb.size();
          cdb.push_back(c);
          freeCIx = max(c.getId()+1, freeCIx);;
        }
      }
    }
  }

  bool checkClubs = false;

  if (checkClubs) {
    vector<wstring> problems;

    for (size_t k=0; k<cdb.size(); k++) {
      pClub pc = &cdb[k];
      pClub pc2 = getClub(pc->getName());
      if (!pc2)
        problems.push_back(pc->getName());
      else if (pc != pc2)
        problems.push_back(pc->getName() + L"-" + pc2->getName());
    }
    problems.begin();
  }
}

void RunnerDB::loadRunners(const wstring &file)
{
  wstring ex = L"Bad runner database. " + file;
  int f=-1;
  _wsopen_s(&f, file.c_str(), _O_BINARY|_O_RDONLY,
            _SH_DENYWR, _S_IREAD|_S_IWRITE);

  if (f!=-1) {
    clearRunners();
    loadedFromServer = false;

    int len = _filelength(f);
    if ( (len%sizeof(RunnerDBEntryV1) != 0) && (len % sizeof(RunnerDBEntry) != 12)) {
      _close(f);
      return;//Failed
    }
    int nentry = 0;

    if (len % sizeof(RunnerDBEntryV2) == 12 || len % sizeof(RunnerDBEntry) == 12) {
      
      int version;
      _read(f, &version, 4);
      _read(f, &dataDate, 4);
      _read(f, &dataTime, 4);
      
      bool migrateV2 = false;
      if (version == 5460002) {
        migrateV2 = true;
        nentry = (len-12) / sizeof(RunnerDBEntryV2);
      }
      else if (version == 5460003) {
        nentry = (len-12) / sizeof(RunnerDBEntry);
      }
      // Else unknown version: fail

      
      rdb.resize(nentry);
      if (rdb.empty()) {
         _close(f);
        return;
      }
      rwdb.resize(rdb.size());
      
      if (!migrateV2) {
        _read(f, &rdb[0], len-12);
        _close(f);
      }
      else {
        vector<RunnerDBEntryV2> rdbV2(nentry);
        _read(f, &rdbV2[0], len-12);
        _close(f);

        for (int k=0;k<nentry;k++) {
          rdb[k].init(rdbV2[k]);
          rwdb[k].init(this, k);
          if (!check(rdb[k]))
            throw meosException(ex);
        }
      }

      for (int k=0;k<nentry;k++) {
        rwdb[k].init(this, k);
        if (!check(rdb[k]))
          throw meosException(ex);
      }
    }
    else {
      dataDate = 0;
      dataTime = 0;

      nentry = len / sizeof(RunnerDBEntryV1);

      rdb.resize(nentry);
      if (rdb.empty()) {
         _close(f);
        return;
      }
      vector<RunnerDBEntryV1> rdbV1(nentry);

      _read(f, &rdbV1[0], len);
      _close(f);
      rwdb.resize(rdb.size());
      RunnerDBEntryV2 tmp;
      for (int k=0;k<nentry;k++) {
        tmp.init(rdbV1[k]);
        rdb[k].init(tmp);
        rwdb[k].init(this, k);
        if (!check(rdb[k]))
          throw meosException(ex);
      }
    }

    int ncard = 0;
    for (int k=0;k<nentry;k++)
      if (rdb[k].cardNo>0)
        ncard++;

    rhash.resize(ncard);

    for (int k=0;k<nentry;k++) {
      if (rdb[k].cardNo>0 && !rdb[k].isRemoved()) {
        rhash[rdb[k].cardNo]=k;
      }
    }
  }
  else throw meosException(ex);
}

bool RunnerDB::check(const RunnerDBEntry &rde) const
{
  if (rde.cardNo<0 || rde.cardNo>99999999
           || rde.name[baseNameLengthUTF-1]!=0 || rde.clubNo<0)
    return false;
  return true;
}

void RunnerDB::updateAdd(const oRunner &r, map<int, int> &clubIdMap)
{
  if (r.getExtIdentifier() > 0) {
    RunnerWDBEntry *dbe = getRunnerById(int(r.getExtIdentifier()));
    if (dbe) {
      dbe->dbe().cardNo = r.CardNo;
      return; // Do not change too much in runner from national database
    }
  }

  const pClub pc = r.Club;
  int localClubId = r.getClubId();

  if (pc) {
    if (clubIdMap.count(localClubId))
      localClubId = clubIdMap[localClubId];

    pClub dbClub = getClub(localClubId);
    bool wrongId = false;
    if (dbClub) {
      if (dbClub->getName() != pc->getName()) {
        dbClub = 0; // Wrong club!
        wrongId = true;
      }
    }

    if (dbClub == 0) {
      dbClub = getClub(r.getClub());
      if (dbClub) {
        localClubId = dbClub->getId();
        clubIdMap[pc->getId()] = localClubId;
      }
    }

    if (dbClub == 0) {
      localClubId = addClub(*pc, wrongId);
      if (wrongId)
        clubIdMap[pc->getId()] = localClubId;
    }
  }

  RunnerWDBEntry *dbe = getRunnerByCard(r.getCardNo());

  if (dbe == 0) {
    dbe = addRunner(r.getName().c_str(), 0, localClubId, r.getCardNo());
    if (dbe)
      dbe->dbe().birthYear = r.getDCI().getInt("BirthYear");
  }
  else {
    if (dbe->getExtId() == 0) { // Only update entries not in national db.
      dbe->setName(r.getName().c_str());
      dbe->dbe().clubNo = localClubId;
      dbe->dbe().birthYear = r.getDCI().getInt("BirthYear");
    }
  }
}

void RunnerDB::getAllNames(vector<wstring> &givenName, vector<wstring> &familyName)
{
  givenName.reserve(rdb.size());
  familyName.reserve(rdb.size());
  for (size_t k=0;k<rwdb.size(); k++) {
    wstring gname(rwdb[k].getGivenName());
    wstring fname(rwdb[k].getFamilyName());
    if (!gname.empty())
      givenName.push_back(gname);
    if (!fname.empty())
      familyName.push_back(fname);
  }
}


void RunnerDB::clearClubs()
{
  clearRunners(); // Runners refer to clubs. Clear runners
  cnhash.clear();
  chash.clear();
  freeCIx = 0;
  cdb.clear();
  if (clubTable)
    clubTable->clear();

}

void RunnerDB::clearRunners()
{
  nhash.clear();
  idhash.clear();
  rhash.clear();
  rdb.clear();
  rwdb.clear();
  if (runnerTable)
    runnerTable->clear();
}

const vector<oDBClubEntry> &RunnerDB::getClubDB(bool checkProblems) const {
  if (checkProblems) {
    for (size_t k = 0; k < cdb.size(); k++) {
      int v = -1;
      if (cdb[k].isRemoved())
        continue;

      // Mark id duplacates as removed
      if (!chash.lookup(cdb[k].getId(), v) || v != k) {
        const_cast<oDBClubEntry &>(cdb[k]).Removed = true;
      }
    }
  }
  return cdb;
}

const vector<RunnerWDBEntry> &RunnerDB::getRunnerDB() const {
  return rwdb;
}

const vector<RunnerDBEntry> &RunnerDB::getRunnerDBN() const {
  return rdb;
}

void RunnerDB::prepareLoadFromServer(int nrunner, int nclub) {
  loadedFromServer = true;
  clearClubs(); // Implicitly clears runners
  cdb.reserve(nclub);
  rdb.reserve(nrunner);
}

void RunnerDB::fillClubs(vector< pair<wstring, size_t> > &out) const {
  out.reserve(cdb.size());
  for (size_t k = 0; k<cdb.size(); k++) {
    if (!cdb[k].isRemoved()) {
      out.push_back(make_pair(cdb[k].getName(), cdb[k].getId()));
    }
  }
  sort(out.begin(), out.end());
}

oDBRunnerEntry::oDBRunnerEntry(oEvent *oe) : oBase(oe) {
  db = 0;
  index = -1;
}

oDBRunnerEntry::~oDBRunnerEntry() {}

void RunnerDB::generateRunnerTableData(Table &table, oDBRunnerEntry *addEntry)
{
  oe->getDBRunnersInEvent(runnerInEvent);
  if (addEntry) {
    addEntry->addTableRow(table);
    return;
  }

  table.reserve(rdb.size());
  oRDB.resize(rdb.size(), oDBRunnerEntry(oe));
  for (size_t k = 0; k<rdb.size(); k++){
    if (!rdb[k].isRemoved()) {
      oRDB[k].init(this, k);
      oRDB[k].addTableRow(table);
    }
  }
}

void RunnerDB::hasEnteredCompetition(__int64 extId) {
  if (runnerTable != 0 && extId!=0) {
    setupIdHash();
    int value;
    if (idhash.lookup(extId, value)) {
      try {
        runnerTable->reloadRow(value + 1);
      }
      catch (const std::exception &) {
        // Ignore any problems with the table.
      }
    }
  }
}

void RunnerDB::refreshTables() {
  if (runnerTable)
    refreshRunnerTableData(*runnerTable);

  if (clubTable)
    refreshClubTableData(*clubTable);
}

void RunnerDB::releaseTables() {
  if (runnerTable)
    runnerTable->releaseOwnership();
  runnerTable = 0;

  if (clubTable)
    clubTable->releaseOwnership();
  clubTable = 0;
}

Table *RunnerDB::getRunnerTB()//Table mode
{
  if (runnerTable == 0) {
    Table *table=new Table(oe, 20, L"Löpardatabasen", "runnerdb");

    table->addColumn("Index", 70, true, true);
    table->addColumn("Id", 70, true, true);
    table->addColumn("Namn", 200, false);
    table->addColumn("Klubb", 200, false);
    table->addColumn("SI", 70, true, true);
    table->addColumn("Nationalitet", 70, false, true);
    table->addColumn("Kön", 50, false, true);
    table->addColumn("Födelseår", 70, true, true);
    table->addColumn("Anmäl", 70, false, true);

    table->setTableProp(Table::CAN_INSERT|Table::CAN_DELETE|Table::CAN_PASTE);
    table->setClearOnHide(false);
    table->addOwnership();
    runnerTable = table;
  }
  int nr = 0;
  for (size_t k = 0; k < rdb.size(); k++) {
    if (!rdb[k].isRemoved())
      nr++;
  }

  if (runnerTable->getNumDataRows() != nr)
    runnerTable->update();
  return runnerTable;
}

void RunnerDB::generateClubTableData(Table &table, oClub *addEntry)
{
  if (addEntry) {
    addEntry->addTableRow(table);
    return;
  }

  table.reserve(cdb.size());
  for (size_t k = 0; k<cdb.size(); k++){
    if (!cdb[k].isRemoved())
      cdb[k].addTableRow(table);
  }
}

void RunnerDB::refreshClubTableData(Table &table) {
  for (size_t k = 0; k<cdb.size(); k++){
    if (!cdb[k].isRemoved()) {
      TableRow *row = table.getRowById(cdb[k].getTableId());
      if (row)
        row->setObject(cdb[k]);
    }
  }
}

void RunnerDB::refreshRunnerTableData(Table &table) {
  for (size_t k = 0; k<oRDB.size(); k++){
    if (!oRDB[k].isRemoved()) {
      TableRow *row = table.getRowById(oRDB[k].getIndex() + 1);
      if (row)
        row->setObject(oRDB[k]);
    }
  }
}

Table *RunnerDB::getClubTB()//Table mode
{
  bool canEdit = !oe->isClient();

  if (clubTable == 0) {
    Table *table = new Table(oe, 20, L"Klubbdatabasen", "clubdb");

    table->addColumn("Id", 70, true, true);
    table->addColumn("Ändrad", 70, false);

    table->addColumn("Namn", 200, false);
    oClub::buildTableCol(oe, table);

    if (canEdit)
      table->setTableProp(Table::CAN_DELETE|Table::CAN_INSERT|Table::CAN_PASTE);
    else
      table->setTableProp(0);

    table->setClearOnHide(false);
    table->addOwnership();
    clubTable = table;
  }

  int nr = 0;
  for (size_t k = 0; k < cdb.size(); k++) {
    if (!cdb[k].isRemoved())
      nr++;
  }

  if (clubTable->getNumDataRows() != nr)
    clubTable->update();
  return clubTable;
}


void oDBRunnerEntry::addTableRow(Table &table) const {
  bool canEdit = !oe->isClient();

  oDBRunnerEntry &it = *(oDBRunnerEntry *)(this);
  table.addRow(index+1, &it);
  if (!db)
    throw meosException("Not initialized");

  RunnerWDBEntry &r = db->rwdb[index];
  RunnerDBEntry &rn = r.dbe();
  
  int row = 0;
  table.set(row++, it, TID_INDEX, itow(index+1), false, cellEdit);

  wchar_t bf[16];
  oBase::converExtIdentifierString(rn.extId, bf);
  table.set(row++, it, TID_ID, bf, false, cellEdit);
  r.initName();
  table.set(row++, it, TID_NAME, r.name, canEdit, cellEdit);

  const pClub pc = db->getClub(rn.clubNo);
  if (pc)
    table.set(row++, it, TID_CLUB, pc->getName(), canEdit, cellSelection);
  else
    table.set(row++, it, TID_CLUB, L"", canEdit, cellSelection);

  table.set(row++, it, TID_CARD, rn.cardNo > 0 ? itow(rn.cardNo) : L"", canEdit, cellEdit);
  wchar_t nat[4] = {wchar_t(rn.national[0]), wchar_t(rn.national[1]), wchar_t(rn.national[2]), 0};

  table.set(row++, it, TID_NATIONAL, nat, canEdit, cellEdit);
  wchar_t sex[2] = {wchar_t(rn.sex), 0};
  table.set(row++, it, TID_SEX, sex, canEdit, cellEdit);
  table.set(row++, it, TID_YEAR, itow(rn.birthYear), canEdit, cellEdit);

  oClass *val = 0;
  bool found = false;

  if (rn.extId != 0)
    found = db->runnerInEvent.lookup(rn.extId, val);

  if (canEdit)
    table.setTableProp(Table::CAN_DELETE|Table::CAN_INSERT|Table::CAN_PASTE);
  else
    table.setTableProp(0);

  if (!found)
    table.set(row++, it, TID_ENTER, L"@+", false, cellAction);
  else
    table.set(row++, it, TID_ENTER, val ? val->getName() : L"", false, cellEdit);
}

const RunnerDBEntry &oDBRunnerEntry::getRunner() const {
 if (!db)
    throw meosException("Not initialized");
  return db->rdb[index];
}

bool oDBRunnerEntry::inputData(int id, const wstring &input,
                           int inputId, wstring &output, bool noUpdate)
{
  if (!db)
    throw meosException("Not initialized");
  RunnerWDBEntry &r = db->rwdb[index];
  RunnerDBEntry &rd = db->rdb[index];

  switch(id) {
    case TID_NAME:
      r.setName(input.c_str());
      r.getName(output);
      db->nhash.clear();
      return true;
    case TID_CARD:
      db->rhash.remove(rd.cardNo);
      rd.cardNo = _wtoi(input.c_str());
      db->rhash.insert(rd.cardNo, index);
      if (rd.cardNo)
        output = itow(rd.cardNo);
      else
        output = L"";
      return true;
    case TID_NATIONAL:
      if (input.empty()) {
        rd.national[0] = 0;
        rd.national[1] = 0;
        rd.national[2] = 0;
      }
      else if (input.size() >= 2) {
        for (size_t i = 0; i < 3; i++) {
          rd.national[i] = i < input.size() ? input[i] : 0; 
        }
      }
      output = r.getNationality();
      break;
    case TID_SEX:
      rd.sex = char(input[0]);
      output = r.getSex();
      break;
    case TID_YEAR:
      rd.birthYear = short(_wtoi(input.c_str()));
      output = itow(r.getBirthYear());
      break;

    case TID_CLUB:
      rd.clubNo = inputId;
      output = input;
      break;
  }
  return false;
}

void oDBRunnerEntry::fillInput(int id, vector< pair<wstring, size_t> > &out, size_t &selected)
{
  RunnerDBEntry &r = db->rdb[index];
  if (id==TID_CLUB) {
    db->fillClubs(out);
    out.push_back(make_pair(L"-", 0));
    selected = r.clubNo;
  }
}

void oDBRunnerEntry::remove() {
  RunnerWDBEntry &r = db->rwdb[index];
  r.remove();
  db->idhash.remove(r.dbe().extId);
  wstring cname(canonizeName(r.name));
  multimap<wstring, int>::const_iterator it = db->nhash.find(cname);

  while (it != db->nhash.end() && cname == it->first) {
    if (it->second == index) {
      db->nhash.erase(it);
      break;
    }
    ++it;
  }

  if (r.dbe().cardNo > 0) {
    int ix = -1;
    if (db->rhash.lookup(r.dbe().cardNo, ix) && ix == index) {
      db->rhash.remove(r.dbe().cardNo);
    }
  }
}

bool oDBRunnerEntry::canRemove() const {
  return true;
}

oDBRunnerEntry *RunnerDB::addRunner() {
  rdb.push_back(RunnerDBEntry());
  rwdb.push_back(RunnerWDBEntry());
  rwdb.back().init(this, rdb.size() -1);

  oRDB.push_back(oDBRunnerEntry(oe));
  oRDB.back().init(this, rdb.size() - 1);

  return &oRDB.back();
}

oClub *RunnerDB::addClub() {
  freeCIx = max<int>(freeCIx + 1, cdb.size());
  while (chash.count(freeCIx))
    freeCIx++;

  cdb.push_back(oDBClubEntry(oe, freeCIx, cdb.size(), this));
  chash.insert(freeCIx, cdb.size()-1);
  cnhash.clear();

  return &cdb.back();
}

oDataContainer &oDBRunnerEntry::getDataBuffers(pvoid &data, pvoid &olddata, pvectorstr &strData) const {
  throw meosException("Not implemented");
}

oDBClubEntry::oDBClubEntry(oEvent *oe, int id, int ix, RunnerDB *dbin) : oClub(oe, id) {
  index = ix;
  db = dbin;
}

oDBClubEntry::oDBClubEntry(const oClub &c, int ix, RunnerDB *dbin) : oClub(c) {
  index = ix;
  db = dbin;
}

oDBClubEntry::~oDBClubEntry() {
}

void oDBClubEntry::remove() {
  Removed = true;
  db->chash.remove(getId());

  vector<wstring> split;
  db->canonizeSplitName(getName(), split);
  for (size_t j = 0; j<split.size(); j++) {
    multimap<wstring, int>::const_iterator it = db->cnhash.find(split[j]);
    while (it != db->cnhash.end() && split[j] == it->first) {
      if (it->second == index) {
        db->cnhash.erase(it);
        break;
      }
      ++it;
    }
  }
}

bool oDBClubEntry::canRemove() const {
  return true;
}

int oDBClubEntry::getTableId() const {
  return index + 1;
}
  
// Link to narrow DB Entry
const RunnerDBEntry &RunnerWDBEntry::dbe() const {
  return owner->rdb[ix];
}

// Link to narrow DB Entry
RunnerDBEntry &RunnerWDBEntry::dbe() {
  return owner->rdb[ix];
}
