/************************************************************************
    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 "oFreeImport.h"
#include "oEvent.h"
#include <algorithm>
#include "gdioutput.h"
#include "gdifonts.h"
#include "localizer.h"
#include "meosexception.h"

#include "meos_util.h"

#include "io.h"
#include "fcntl.h"
#include "sys/stat.h"
#include <cassert>
#include <algorithm>

void oWordDB::insert(const wchar_t *s)
{
  str.insert(s);
}

bool oWordDB::lookup(const wchar_t *s) const
{
  return str.count(s)==1;
}

const char *oWordDB::deserialize(const char *bf, const char *end)
{
  BYTE s=bf[0];
  bf++;
  str.clear();
  for (int k=0;k<s;k++) {
    wstring ns((wchar_t*)bf);
    //wstring n(ns.begin(), ns.end());
    bf+=(ns.size()+1)*sizeof(wchar_t);

    if (bf>end)
      throw std::exception("Internal error deserializing wordlist.");

    str.insert(ns);
  }
  return bf;
}

char *oWordDB::serialize(char *bf) const
{
  //Randomize order for better search performace on reload.
  BYTE s=BYTE(str.size());
  vector<wstring> rnd(s);
  int k=0;
  for (set<wstring>::const_iterator it=str.begin(); it!=str.end(); ++it)
    rnd[k++]=*it;

  bf[0]=s;
  bf++;
  for (k=0;k<s;k++) {
    int i=rand() % rnd.size();
    int byteSize = (rnd[i].size()+1) * sizeof(wchar_t);
    memcpy(bf, rnd[i].c_str(), byteSize);
    bf+=byteSize;
    swap(rnd[i], rnd.back());
    rnd.pop_back();
  }
  return bf;
}

int oWordDB::serialSize() const
{
  int s=1;
  for(set<wstring>::const_iterator it=str.begin(); it!=str.end(); ++it)
    s+=(it->size()+1)*sizeof(wchar_t);

  return s;
}

pWordDatabase oWordDB::split()
{
  if (str.size()>=hashSplitSize) {
    oWordIndexHash *db=new oWordIndexHash(false);

    set<wstring>::iterator it;

    for(it=str.begin();it!=str.end();++it)
      db->insert(it->c_str());

    delete this;
    return db;
  }
  else return this;
}

size_t oWordDB::size()
{
  return str.size();
}

void oWordIndexHash::clear()
{
  MapTable::iterator it;

  for(it=unMapped.begin(); it!=unMapped.end(); ++it)
    if (it->second)
      delete it->second;

  unMapped.clear();

  for(int k=0;k<hashTableSize;k++)
    if (hashTable[k]) {
      delete hashTable[k];
      hashTable[k]=0;
    }
}

const char *oWordIndexHash::deserialize(const char *bf, const char *end)
{
  clear();
  unsigned short s=*((short *)&bf[0]);
  bf+=2;

  for (int k=0;k<s;k++) {
    unsigned short a=*((short *)&bf[0]);
    unsigned short b=*((short *)&bf[2]);
    pWordDatabase db=0;
    bf+=4;
    if (a) {
      if (b==1)
        db = new oWordDB;
      else if (b==2)
        db = new oWordIndexHash(false);
      else
        throw std::exception("Internal error deserilizing wordlist.");

      bf = db->deserialize(bf, end);
    }

    DWORD i=a-indexMapStart;
    if (i<=(indexMapEnd-indexMapStart)) {
      if (hashTable[i])
        throw std::exception("Internal error deserilizing wordlist.");
      else hashTable[i]=db;
    }
    else
      unMapped[a]=db;
  }
  return bf;
}

char *oWordIndexHash::serialize(char *bf) const
{
  unsigned short s = WORD(unMapped.size());
  for (int k=0;k<hashTableSize;k++)
    if (hashTable[k])
      s++;

  *((short *)&bf[0])=s;
  bf+=2;

  for(MapTable::const_iterator it=unMapped.begin(); it!=unMapped.end(); ++it) {
    *((short *)&bf[0]) = it->first;
    if (it->first) {
      *((short *)&bf[2]) = it->second->getType();
      bf = it->second->serialize(bf+4);
    }
    else { //Empty string.
      *((short *)&bf[2]) = 0;
      bf+=4;
    }
  }

  for (int k=0;k<hashTableSize;k++) {
    if (hashTable[k]) {
      *((short *)&bf[0]) = k + indexMapStart ;
      *((short *)&bf[2]) = hashTable[k]->getType();
      bf = hashTable[k]->serialize(bf+4);
    }
  }
  return bf;
}

int oWordIndexHash::serialSize() const
{
  int s=2;
  for (int k=0;k<hashTableSize;k++) {
    if (hashTable[k])
      s+=hashTable[k]->serialSize()+4;
  }
  for(MapTable::const_iterator it=unMapped.begin(); it!=unMapped.end(); ++it)
    s+=it->second ? it->second->serialSize()+4 : 4;

  return s;
}

void oWordIndexHash::insert(const wchar_t *s)
{
  DWORD i=s[0]-indexMapStart;

  if (i<=(indexMapEnd-indexMapStart)) {
    if (!hashTable[i]) {
      hashTable[i]=new oWordDB();
      hashTable[i]->insert(s+1);
    }
    else {
      hashTable[i]=hashTable[i]->split();
      hashTable[i]->insert(s+1);
    }
  }
  else {
    if (s[0]==0)
      unMapped[0]=0; //Empty string
    else {
      MapTable::iterator it=unMapped.find(s[0]);
      if (it==unMapped.end()) {
        pWordDatabase db(hashAll ? pWordDatabase(new oWordIndexHash(false)):
                                   pWordDatabase(new oWordDB()));
        db->insert(s+1);
        unMapped[s[0]]=db;
      }
      else {
        if (it->second) {
          it->second=it->second->split();
          it->second->insert(s+1);
        }
        else
          assert(s[0]==0);
      }
    }
  }
}

bool oWordIndexHash::lookup(const wchar_t *s) const
{
  DWORD i = s[0] - indexMapStart;

  if (i<=(indexMapEnd-indexMapStart)) {
    if (!hashTable[i])
      return false;
    else
      return hashTable[i]->lookup(s+1);
  }
  else {
    MapTable::const_iterator it=unMapped.find(s[0]);
    if (it==unMapped.end())
      return false;
    else if (s[0]==0)
      return true;
    else
      return it->second->lookup(s+1);
  }
}

oWordIndexHash::oWordIndexHash(bool hashAll_) : hashAll(hashAll_)
{
  for(int k=0;k<hashTableSize;k++)
    hashTable[k]=0;
}

oWordIndexHash::~oWordIndexHash()
{
  clear();
}

oWordList::oWordList() : wh(true) {}

oWordList::~oWordList() {}

void oWordList::insert(const wchar_t *s)
{
  int len=wcslen(s);
  if (len<511) {
    wchar_t bf[512];
    wcscpy_s(bf, s);
    CharLowerBuff(bf, len);
    wh.insert(bf);
  }
  else wh.insert(s);
}

bool oWordList::lookup(const wchar_t *s) const
{
  int len=wcslen(s);
  if (len<511) {
    wchar_t bf[512];
    wcscpy_s(bf, s);
    CharLowerBuff(bf, len);
    return wh.lookup(bf);
  }
  else return wh.lookup(s);
}

void oWordList::serialize(vector<char> &serial) const
{
  int s=wh.serialSize();
  serial.resize(s);
  wh.serialize(&serial[0]);
}

void oWordList::deserialize(const vector<char> &serial)
{
  wh.clear();
  wh.deserialize(&serial[0], &serial[0]+serial.size());
}

void oWordList::save(const wstring &file) const
{
  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) {
    vector<char> serial;
    serialize(serial);
    char *hdr="WWDB";

    _write(f, hdr, 4);
    DWORD s=serial.size();
    _write(f, &s, 4);
    _write(f, &serial[0], s);

    _close(f);
  }
  else throw std::exception("Could not save word database.");
}

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

  if (f!=-1) {
    char hdr[5]={0,0,0,0,0};
    _read(f, hdr, 4);

    if ( strcmp(hdr, "WWDB")!=0 )
      throw meosException(ex);

    DWORD s=0;
    _read(f, &s, 4);

    vector<char> serial(s);

    if (_read(f, &serial[0], s)!=s)
      throw meosException(ex);

    _close(f);

    deserialize(serial);
  }
  else throw meosException(ex);
}

oFreeImport::oFreeImport(void)
{
  
  for(int k=1;k<30;k++)
    separator.insert(k);

  separator.insert(' ');
  separator.insert(',');
  separator.insert(';');
  separator.insert('(');
  separator.insert(')');
  separator.insert('/');

  loaded=false;
}

oFreeImport::~oFreeImport(void)
{
}

wchar_t *trim(wchar_t *str)
{
  int k=wcslen(str)-1;
  while(k>=0 && (iswspace(str[k]) || str[k] == 160))
    str[k--] = 0;

  while(iswspace(*str) || str[k]==160)
    str++;

  return str;
}

wchar_t *oFreeImport::extractPart(wchar_t *&str, int &wordCount) const
{
  wordCount=0;
  while (separator.count(*str))
    str++;

  if (!*str)
    return str;

  wchar_t *out=str;

  wordCount=1;
  while (*str && (!separator.count(*str) || *str==' ')) {
    if (*str==' ') {
      while (*++str==' ');
      if (separator.count(*str) || !*str) {
        if (*str)
          *str++=0;
        return trim(out);
      }
      wordCount++;
    }
    str++;
  }
  if (*str)
    *str++=0;
  return trim(out);
}

wchar_t *oFreeImport::extractWord(wchar_t *&str, int &count) const {
  count=0;
  while (separator.count(*str))
    str++;

  if (!*str)
    return str;

  wchar_t *out=str;

  count=1;
  while (*str && !separator.count(*str)) {
    count++;
    str++;
  }
  if (*str)
    *str++=0;

  return out;
}

wchar_t *oFreeImport::extractLine(wchar_t *&str, int &count) const
{
  count=0;

  while (*str=='\n')
    str++;

  wchar_t *out=str;

  while (*str && *str!='\n') {
    str++;
    count++;
  }

  if (*str)
    *str++=0;

  return out;
}


oEntryPerson::oEntryPerson(const wstring &clb)
{
  club = clb;
  cardNo=0;
}

void oEntryPerson::swap()
{
  if (!name1.empty())
    std::swap(name1, name2);
}

int oEntryPerson::nameCount() const {
  if (!name2.empty())
    return countWords(name1.c_str()) + countWords(name2.c_str());
  else
    return countWords(name1.c_str());
}

void oEntryBlock::clear(const wstring &rulingClub, const wstring &rulingClass)
{
  ePersons.clear();
  eClub = rulingClub;
  eClass = rulingClass;
  eStartTime.clear();
  canInsertName = true;
  isClassSet = false; // Count only explicit set class
  nClubsSet = rulingClub.empty() ? 0 : 1;
}

oEntryBlock::oEntryBlock(const oFreeImport &importer) : freeImporter(importer)
{
  canInsertName=true;
  nClubsSet = 0;
  isClassSet = false;
}

oEntryBlock::oEntryBlock(const oEntryBlock &eb) : freeImporter(eb.freeImporter)
{
  ePersons = eb.ePersons;
  eClub = eb.eClub;
  eClass = eb.eClass;
  eStartTime = eb.eStartTime;
  canInsertName = eb.canInsertName;
  isClassSet = eb.isClassSet;
  nClubsSet = eb.nClubsSet;
}

void oEntryBlock::operator=(const oEntryBlock &eb)
{
  ePersons = eb.ePersons;
  eClub = eb.eClub;
  eClass = eb.eClass;
  eStartTime = eb.eStartTime;
  canInsertName = eb.canInsertName;
  isClassSet = eb.isClassSet;
  nClubsSet = eb.nClubsSet;
}

void oEntryBlock::setClass(const wchar_t *s)
{
  eClass=s;
  if (!ePersons.empty())
    canInsertName=false;
  isClassSet = true;
  completeName();
}

void oEntryBlock::setClub(const wchar_t *s)
{
  eClub = s;
  nClubsSet++;
  for (size_t k = 0; k<ePersons.size(); k++)
    if (ePersons[k].club.empty())
      ePersons[k].club = eClub;
  if (nClubsSet == ePersons.size())
    ePersons.back().club = eClub;

  if (int(ePersons.size())>=freeImporter.getExpectedNumRunners(eClass))
    canInsertName=false;

  completeName();
}

void oEntryBlock::completeName() {
  if (!ePersons.empty() && !ePersons.back().name1.empty()
                         && ePersons.back().name2.empty())
    ePersons.back().name2=L"*";
}

void oEntryBlock::setStartTime(const wchar_t *s)
{
  eStartTime=s;
  if (!ePersons.empty())
    canInsertName=false;

  completeName();
}

void oEntryBlock::setCardNo(int c)
{
  if (ePersons.empty() || ePersons.back().cardNo!=0)
    ePersons.push_back(oEntryPerson(eClub));
  
  for (size_t i = 0; i < ePersons.size(); i++) {
    if (ePersons[i].cardNo == 0) {
      ePersons[i].cardNo=c;
      break;
    }
  }
  completeName();
}

bool oEntryBlock::needCard() const {  
  if (ePersons.empty())
    return true;

  for (size_t i = 0; i < ePersons.size(); i++) {
    if (ePersons[i].cardNo == 0) {
      return true;
    }
  }
  return false;
}

wstring oEntryBlock::getTeamName() const
{
  set<wstring> clubs;
  if (!eClub.empty())
    clubs.insert(eClub);
  for (size_t k = 0; k<ePersons.size(); k++)
    if (!ePersons[k].club.empty())
      clubs.insert(ePersons[k].club);

  if (clubs.size() == 1)
    return *clubs.begin();
  else if (clubs.size() > 1) {
    wstring tname;
    for (set<wstring>::iterator it = clubs.begin(); it != clubs.end(); ++it) {
      if (!tname.empty())
        tname += L"/";
      tname += *it;
    }
    return tname;
  }
  else
    return lang.tl("Lag");
}

wstring oEntryBlock::getName(int k) const
{
  if (size_t(k)>=ePersons.size())
    return lang.tl("N.N.");

  wstring n=ePersons[k].name1;

  if (!ePersons[k].name2.empty())
    n+=L" "+ePersons[k].name2;

  return n;
}

wstring oEntryBlock::getClub(int k) const
{
  if (size_t(k)>=ePersons.size())
    return eClub;

  const wstring &c=ePersons[k].club;
  if (!c.empty())
    return c;

  return eClub;
}


int oEntryBlock::getCard(int k) const
{
  if (k<int(ePersons.size()))
    return ePersons[k].cardNo;
  else
    return 0;
}


vector<wstring> oEntryBlock::getPersons() const
{
  vector<wstring> names;
  wstring n;
  wchar_t bf[256];
  for (size_t k=0;k<ePersons.size();k++) {
    n+=ePersons[k].name1;

    if (!ePersons[k].name2.empty())
      n+=L" "+ePersons[k].name2;

    if (ePersons[k].cardNo>0) {
      swprintf_s(bf, L" (%d)", ePersons[k].cardNo);
      n+=bf;
    }


    names.push_back(n);
    n.clear();
  }
  return names;
}

int oEntryBlock::nameCount()
{
  if (ePersons.empty())
    return 0;
  int space=0;
  const wstring &name1=ePersons.back().name1;
  const wstring &name2=ePersons.back().name2;

  if (!name1.empty()) {
    space++;
    for (size_t k=0;k<name1.size();k++)
      if (name1[k]==' ')
        space++;
  }
  if (!name2.empty()) {
    space++;
    for (size_t k=0;k<name1.size();k++)
      if (name1[k]==' ')
        space++;
  }
  return space;
}

void oEntryBlock::addPerson(const wchar_t *s, bool complete)
{
  if (ePersons.empty())
    ePersons.push_back(oEntryPerson(eClub));

  if (ePersons.back().nameCount()>=2 && countWords(s)>=2)
    completeName();

  if (complete) {
    if (!ePersons.back().name1.empty())
      ePersons.push_back(oEntryPerson(eClub));

    ePersons.back().name1=s;
  }
  else {
    if (ePersons.back().name1.empty())
      ePersons.back().name1=s;
    else if (ePersons.back().name2.empty())
      ePersons.back().name2=s;
    else {
      ePersons.push_back(oEntryPerson(eClub));
      ePersons.back().name1=s;
    }
  }
}

bool oEntryBlock::acceptMoreClubs(int expectedNumRunners) const
{
  return nClubsSet < int(ePersons.size()) ||
         nClubsSet < expectedNumRunners;
}

bool oEntryBlock::expectMoreNames(int expectedNumRunners) const
{
  return int(ePersons.size()) < expectedNumRunners ||
         (expectedNumRunners == ePersons.size() &&
                 ePersons.back().nameCount()<=1);
}


void oEntryBlock::cleanEntry()
{
  for (size_t k=0;k<ePersons.size(); k++) {
    if (ePersons[k].name1.empty()) {
      ePersons.resize(k, oEntryPerson(L"")); //Delete this entry
      return;
    }
    else if (ePersons[k].name2==L"*") //Fix completed names.
      ePersons[k].name2.clear();
  }
}

/*
bool oFreeImport::analyze(vector<bool> &b, int &offset, int &delta) const
{
  vector<int> d;
  d.reserve(b.size()/3);

  for (size_t k=0;k<b.size();k++)
    if (b[k]) d.push_back(k);

  if (d.size()<2) {
    delta=0;
    offset=0;
    return false;
  }

  offset=d[0];

  delta=0;
  for (size_t k=1;k<d.size();k++)
    delta=gcd(delta, d[k]-d[k-1]);

  if (delta>1) {
    int x=offset;
    while(unsigned(x)<b.size())
      b[x]=true, x+=delta;

    return true;
  }
  else
    return false;

}*/

bool oFreeImport::isCard(const wchar_t *p) const
{
  int k=_wtoi(p);
  return (p[0]=='0' && p[1]==0) || (k>=1000 && k<10000000);
}

bool oFreeImport::isTime(const wstring &m) const
{
  if (m.empty() || m.length()>9)
    return false;

  for(size_t k=0;k<m.length();k++)
    if (!isdigit(BYTE(m[k])) && !(m[k]==':' || m[k]=='.'))
      return false;

  int hour=_wtoi(m.c_str());
  if (hour<0 || hour>23)
    return false;

  int minute=0;
  int second=0;
  int kp=m.find_first_of(':');

  if (kp>0) {
    wstring mtext=m.substr(kp+1);
    minute=_wtoi(mtext.c_str());

    if (minute<0 || minute>60)
      return false;

    kp=mtext.find_last_of(':');

    if (kp>0) {
      second=_wtoi(mtext.substr(kp+1).c_str());
      if (second<0 || second>60)
        return false;
    }
  }
  int t=hour*3600+minute*60+second;
  if (t<=0)
    return false;

  return true;
}

bool oFreeImport::isCrap(const wchar_t *p) const
{
  int s=wcslen(p);

  for (int k=0;k<s;k++) {
    if (p[k]=='(' || p[k]==')' ||
        (p[k]>='0' && p[k]<='9'))
      return true;
  }
  return false;
}

bool oFreeImport::isName(const wchar_t *p) const
{
  wchar_t bf[1024];
  wcscpy_s(bf, p);
  wchar_t *str=bf;
  int c=1;
  vector<const wchar_t *> w;

  while(c>0) {
    const wchar_t *out= extractWord(str,c);
    if (c>0)
      w.push_back(out);
  }
  if (w.size()==1)
    return familyDB.lookup(w[0]) || givenDB.lookup(w[0]);
  else if (w.size()<4) {
    int n=0;
    int t=(w.size()+1)/2;
    for (size_t k=0;k<w.size();k++) {
      n+=familyDB.lookup(w[k]) || givenDB.lookup(w[k]) ? 1:0;
      if (isCard(w[k]))
        return false;
      if (classDB.lookup(w[k]))
        return false;
    }
    return n>=t;
  }

  return false;
}

void oFreeImport::analyzePart(wchar_t *part, const MatchPattern &ptrn, int nNamesPerPart,
                              oEntryBlock &entry, vector<oEntryBlock> &entries, bool allowName)
{
  vector<const wchar_t *> words;
  int count=1;

  while (count>0) {
    const wchar_t *out=extractWord(part, count);
    if (count>0)
      words.push_back(out);
  }

  allowName &= !entry.hasName() || nNamesPerPart!=1;

  vector<bool> isNameV(words.size(), false);
  vector<bool> isClub(words.size(), false);
  vector<bool> isClass(words.size(), false);
  vector<bool> isCardV(words.size(), false);
  vector<bool> isTimeV(words.size(), false);
  vector<bool> used(words.size(), false);

  int names=0;
  int classes=0;
  int clubs=0;
  int cards=0;
  int times=0;

  for (size_t j=0;j<words.size();j++) {
    if (familyDB.lookup(words[j]) || givenDB.lookup(words[j])) {
      isNameV[j]=true;
      names++;
    }
    if (clubDB.lookup(words[j])) {
      isClub[j]=true;
      clubs++;
    }
    if (classDB.lookup(words[j])) {
      isClass[j]=true;
      classes++;
    }
    if (isCard(words[j])) {
      isCardV[j]=true;
      cards++;
    }
    if (isTime(words[j])) {
      isTimeV[j]=true;
      times++;
    }
  }

  if (allowName) {
    if (names>0 && (clubs+classes+cards+times)<1) {
      if (!entry.canInsertName) {
        entry.cleanEntry();
        entries.push_back(entry);
        entry.clear(rulingClub, rulingClass);
      }
      wstring n;
      for (size_t j=0;j<words.size();j++) {
        if (!isCrap(words[j]))
          n+=wstring(words[j])+L" ";
      }
      n.resize(n.size()-1);
      entry.addPerson(n.c_str(), false);
      lastInsertedType = Name;
      return;
    }
  }
  wstring cls;
  wstring clb;
  wstring name;
  Types lastType = Name; //Name
  if (entry.nameCount()>0 && !entry.expectMoreNames(getExpectedNumRunners(entry.eClass))
    && !allowName)
    lastType = Club; //Guess club

  for (size_t k=0;k<words.size();) {
    if (isTimeV[k]) {
      entry.setStartTime(words[k]);
      lastInsertedType = Time;
      used[k]=true, k++;
      if (entry.expectMoreNames(getExpectedNumRunners(entry.eClass)))
        lastType = Name;
      else lastType = Unknown;
    }
    else if (isCardV[k]) {
      entry.setCardNo(_wtoi(words[k]));
      lastInsertedType = Card;
      used[k]=true, k++;
      lastType = Name;
      if (entry.expectMoreNames(getExpectedNumRunners(entry.eClass)))
        lastType = Name;
      else lastType = Unknown;
    }
    else if (isClass[k]) {
      while(k<words.size() && isClass[k])
        cls+=wstring(words[k])+L" ", used[k]=true, k++;
      lastType = Class;
    }
    else if (isClub[k]) {
      while(k<words.size() && isClub[k])
        clb+=wstring(words[k])+L" ", used[k]=true, k++;
      lastType = Club;
    }
    else if (isNameV[k] && allowName) {
      while(k<words.size() && isNameV[k]) {
        if (!isCrap(words[k]))
          name+=wstring(words[k])+L" ";
        used[k]=true, k++;
      }
      lastType = Name;
    }
    else {
      if (lastType == Name) {
        if (!isCrap(words[k]))
          name+=wstring(words[k])+L" ";
        k++;
      }
      else if (lastType == Class)
        cls+=wstring(words[k])+L" ", k++;
      else if (lastType == Club)
        clb+=wstring(words[k])+L" ", k++;
      else k++;
    }
  }

  if (!clb.empty()) {
    if (lastInsertedType == None)
      rulingClub = clb;

    clb.resize(clb.size()-1);
    entry.setClub(clb.c_str());
    lastInsertedType = Club;
  }
  if (!cls.empty()) {
    if (lastInsertedType == None)
      rulingClass = cls;
    cls.resize(cls.size()-1);
    entry.setClass(cls.c_str());
    lastInsertedType = Class;
  }
  if (!name.empty()) {
    name.resize(name.size()-1);
    entry.addPerson(name.c_str(), words.size()>2);
    lastInsertedType = Name;
  }
}

void oFreeImport::test(const oRunnerList &li)
{
  oRunnerList::const_iterator it;
  //classDB.insert("H21");
  //classDB.insert("Herrar");

  for(it=li.begin();it!=li.end();++it) {
    givenDB.insert(it->getGivenName().c_str());
    familyDB.insert(it->getFamilyName().c_str());
    clubDB.insert(it->getClub().c_str());
    classDB.insert(it->getClass(true).c_str());
  }


  wstring b(L"0"), c(L"a2"), d(L"a");

  const int end='z'+5;
  for(int k='a';k<=end+1;k++)
    if (k>end)
      givenDB.insert(b.c_str());
    else {
      wstring z=b+wchar_t(k);
      for( int j='a';j<=end+1;j++) {
        if (j>end)
          givenDB.insert(z.c_str());
        else {
          givenDB.insert( (z+wchar_t(j)+d).c_str() );
        }
      }
    }

  givenDB.save(L"test.mwd");
  oWordList wl;
  wl.load(L"test.mwd");
  wl.save(L"test2.mwd");
  for(it=li.begin();it!=li.end();++it)
    givenDB.insert(it->getGivenName().c_str());

  wl.save(L"test3.mwd");
}

/** Must not return 0 */
int oFreeImport::getExpectedNumRunners(const wstring &cls) const
{
  wstring key(canonizeName(cls.c_str()));
  map<wstring,int>::const_iterator it = runnersPerClass.find(key);

  if (it != runnersPerClass.end())
    return it->second;
  else
    return 1;
}

void oFreeImport::init(const oRunnerList &r, const oClubList &clb, const oClassList &cls)
{
  runnersPerClass.clear();
  vector<wstring> split_vector;
  wstring sep(L" ");

  for(oRunnerList::const_iterator it=r.begin();it!=r.end();++it) {
    givenDB.insert(it->getGivenName().c_str());
    wstring f=it->getFamilyName();
    familyDB.insert(f.c_str());

    split(f, sep, split_vector);
    if (split_vector.size()>1)
      for(size_t k=0;k<split_vector.size();k++) {
        familyDB.insert(split_vector[k].c_str());
      }
  }

  for(oClubList::const_iterator it=clb.begin();it!=clb.end();++it) {
    clubDB.insert(it->getName().c_str());
    split(it->getName().c_str(), sep, split_vector);
    if (split_vector.size()>1)
      for(size_t k=0;k<split_vector.size();k++) {
        clubDB.insert(split_vector[k].c_str());
      }
  }

  for(oClassList::const_iterator it=cls.begin();it!=cls.end();++it) {
    wstring nname(it->getName().begin(), it->getName().end());
    classDB.insert(nname.c_str());
    split(nname.c_str(), sep, split_vector);
    if (split_vector.size()>1)
      for(size_t k=0;k<split_vector.size();k++) {
        classDB.insert(split_vector[k].c_str());
      }
    int numrunner = it->getNumDistinctRunners();
    wstring key(canonizeName(nname.c_str()));
    runnersPerClass[key] = numrunner;
  }

  const wchar_t *clsPrefix[8] = {L"h", L"d", L"m", L"w",
                       L"herrar", L"damer", L"men", L"women"};

  wchar_t bf[64];

  vector<int> ages;

  for (int k=10;k<=16;k++)
    ages.push_back(k);
  ages.push_back(18);
  ages.push_back(20);
  ages.push_back(21);
  for (int k=35;k<=100;k+=5)
    ages.push_back(k);

  for (int k=0;k<8;k++) {
    classDB.insert(clsPrefix[k]);

    for (size_t j=0; j<ages.size();j++) {
      swprintf_s(bf, L"%d", ages[j]);
      classDB.insert(bf);

      swprintf_s(bf, L"%s%d", clsPrefix[k], ages[j]);
      classDB.insert(bf);
      swprintf_s(bf, L"%s %d", clsPrefix[k], ages[j]);
      classDB.insert(bf);

      swprintf_s(bf, L"%s%dM", clsPrefix[k], ages[j]);
      classDB.insert(bf);
      swprintf_s(bf, L"%s%dL", clsPrefix[k], ages[j]);
      classDB.insert(bf);
      swprintf_s(bf, L"%s%dK", clsPrefix[k], ages[j]);
      classDB.insert(bf);

      swprintf_s(bf, L"%s %dM", clsPrefix[k], ages[j]);
      classDB.insert(bf);
      swprintf_s(bf, L"%s %dL", clsPrefix[k], ages[j]);
      classDB.insert(bf);
      swprintf_s(bf, L"%s %dK", clsPrefix[k], ages[j]);
      classDB.insert(bf);

      swprintf_s(bf, L"%s%d M", clsPrefix[k], ages[j]);
      classDB.insert(bf);
      swprintf_s(bf, L"%s%d L", clsPrefix[k], ages[j]);
      classDB.insert(bf);
      swprintf_s(bf, L"%s%d K", clsPrefix[k], ages[j]);
      classDB.insert(bf);

      swprintf_s(bf, L"%s%d Lång", clsPrefix[k], ages[j]);
      classDB.insert(bf);
      swprintf_s(bf, L"%s%d Kort", clsPrefix[k], ages[j]);
      classDB.insert(bf);
      swprintf_s(bf, L"%s%d Motion", clsPrefix[k], ages[j]);
      classDB.insert(bf);

      if (ages[j]>=18 && ages[j]<=21) {
        swprintf_s(bf, L"%s %dE", clsPrefix[k], ages[j]);
        classDB.insert(bf);
        swprintf_s(bf, L"%s %d Elit", clsPrefix[k], ages[j]);
        classDB.insert(bf);
        swprintf_s(bf, L"%s%dE", clsPrefix[k], ages[j]);
        classDB.insert(bf);
        swprintf_s(bf, L"%s%d Elit", clsPrefix[k], ages[j]);
        classDB.insert(bf);
      }
    }
  }
  classDB.insert(L"L");
  classDB.insert(L"Lång");
  classDB.insert(L"E");
  classDB.insert(L"Elit");
  classDB.insert(L"Elite");
  classDB.insert(L"Adult");
  classDB.insert(L"Vuxen");
  classDB.insert(L"Insk");
  classDB.insert(L"Insk.");
  classDB.insert(L"Inskolning");
  classDB.insert(L"M");
  classDB.insert(L"ÖM");
  classDB.insert(L"Motion");
  classDB.insert(L"K");
  classDB.insert(L"Kort");
  classDB.insert(L"Mellan");
  classDB.insert(L"Svår");
  classDB.insert(L"Lätt");
  classDB.insert(L"Kortlätt");
  classDB.insert(L"Långsvår");
  classDB.insert(L"km");
  classDB.insert(L"Short");
  classDB.insert(L"Long");
  classDB.insert(L"Öppen");
  classDB.insert(L"Ö");
  classDB.insert(L"Ungdom");
  classDB.insert(L"Ung.");
  classDB.insert(L"U");
  classDB.insert(L"U1");
  classDB.insert(L"U2");
  classDB.insert(L"U3");
  classDB.insert(L"U4");

  for (int j=1;j<10;j++) {
    swprintf_s(bf, L"Ö%d", j);
    classDB.insert(bf);
    swprintf_s(bf, L"Ö %d", j);
    classDB.insert(bf);
    swprintf_s(bf, L"Öppen %d", j);
    classDB.insert(bf);
    swprintf_s(bf, L"D%d", j);
    classDB.insert(bf);
    swprintf_s(bf, L"Direkt %d", j);
    classDB.insert(bf);
  }
}

bool oFreeImport::isHeaderWord(const wstring &word) const {
  if (headerWords.empty()) {
    headerWords.insert(canonizeName(L"namn"));
    headerWords.insert(canonizeName(L"klass"));
    headerWords.insert(canonizeName(L"ålder"));
    headerWords.insert(canonizeName(L"anmälda"));
    headerWords.insert(canonizeName(L"anmälningar"));
    headerWords.insert(canonizeName(L"deltagare"));
    headerWords.insert(canonizeName(L"löpare"));
    headerWords.insert(canonizeName(L"bricka"));
    headerWords.insert(canonizeName(L"starttid"));
    headerWords.insert(canonizeName(L"nummer"));
    headerWords.insert(canonizeName(L"startnummer"));
    headerWords.insert(canonizeName(L"bricknummer"));
    headerWords.insert(canonizeName(L"nummer"));
    headerWords.insert(canonizeName(L"klubb"));
    headerWords.insert(canonizeName(L"klassnamn"));
    headerWords.insert(canonizeName(L"pinne"));
    headerWords.insert(canonizeName(L"nummerlapp"));
    headerWords.insert(canonizeName(L"SI"));
    headerWords.insert(canonizeName(L"förening"));
    headerWords.insert(canonizeName(L"rank"));
    headerWords.insert(canonizeName(L"ranking"));
    headerWords.insert(canonizeName(L"nr-lapp"));
    headerWords.insert(canonizeName(L"tid"));

    headerWords.insert(canonizeName(L"name"));
    headerWords.insert(canonizeName(L"age"));
    headerWords.insert(canonizeName(L"class"));
    headerWords.insert(canonizeName(L"start"));
    headerWords.insert(canonizeName(L"time"));
    headerWords.insert(canonizeName(L"club"));
    headerWords.insert(canonizeName(L"card"));
  }
  return headerWords.count(canonizeName(word.c_str()))>0;
}

bool match_char(wchar_t c, const wstring &sep) {
  for (size_t j = 0; j<sep.size(); j++)
    if (c == sep[j])
      return true;

  return false;
}

bool oFreeImport::analyzeHeaders(vector<wchar_t *> &line) const
{
  if (line.empty())
    return true;

  int header = 0;
  int nonheader = 0;
  bool forceNoHeader = false;
  vector<wstring> firstWord;
  const wstring separators = L" :,.=";
  for (size_t k = 0; k < line.size(); k++) {
    vector<wstring> words;
    split(line[k], separators, words);
    if (words.empty())
      firstWord.push_back(L"");
    else
      firstWord.push_back(words.front());

    for (size_t j = 0; j < words.size(); j++) {
      if (words[j].empty())
        continue;
      if (isHeaderWord(words[j]) )
        header++;
      else {
        nonheader++;
        // If the header contains a real name, class or club, do not reject
        if (isName(words[j].c_str()))
          forceNoHeader = true;

        if (classDB.lookup(words[j].c_str()))
          forceNoHeader = true;
        if (clubDB.lookup(words[j].c_str()) || clubDB.lookup(line[k]))
          forceNoHeader = true;
      }
    }
  }

  if (!forceNoHeader && header > nonheader)
    return true;

  // Remove header words
  for (size_t k = line.size()-1; k < line.size(); k--) {
    if (isHeaderWord(line[k])) {
      line.erase(line.begin()+k);
    }
    else if (isHeaderWord(firstWord[k])) {
      line[k] += firstWord[k].size();
      int j = 0;
      while (line[k][j] && match_char(line[k][j], separators))
        j++;
      line[k] += j;
    }
  }
  return false;
}

const int CLEARROW = 1;
const int IGNOREROW = 2;
const int COMPLETEROW = 4;

int oFreeImport::preAnalyzeRow(vector<wchar_t *> &p,
                               const vector<MatchPattern> &ptrn,
                               vector<int> &classified)
{
  int stat = 0;

  int names = 0;
  int clubs = 0;
  int cards = 0;
  int classes = 0;
  int unknown = 0;

  for (size_t j=0;j<p.size();j++) {
    int type=-1;
    if (ptrn[j].isClub() && clubDB.lookup(p[j]))
      type=0;
    else if (ptrn[j].isClass() && classDB.lookup(p[j]))
      type=1;
    else if (ptrn[j].isTime() && isTime(p[j]))
      type=2;
    else if (ptrn[j].isCard() && isCard(p[j]))
      type=3;
    else if (ptrn[j].isName() && isName(p[j]))
      type=4;
    else if (clubDB.lookup(p[j]))
      type=0;
    else if (classDB.lookup(p[j]))
      type=1;
    else if (isTime(p[j]))
      type=2;
    else if (isCard(p[j]))
      type=3;
    else if (isName(p[j]))
      type=4;

    if (type==-1 && ptrn.size()>1) {
      if (ptrn[j].isClub())
        type=0;
      else if (ptrn[j].isClass())
        type=1;
      else if (ptrn[j].isCard() && _wtoi(p[j])>0)
        type=3;
    }

    classified.push_back(type);
    if (type == 1)
      classes++;
    else if (type==3)
      cards++;
    else if (type==4)
      names++;
    else if (type==0)
      clubs++;
    else
      unknown++;

    const wchar_t *canP = canonizeName(p[j]);
    if (wcscmp(canP, L"vakant")==0 || wcscmp(canP, L"vacant")==0)
      stat|=IGNOREROW;
  }

  if (clubs>0 && names>0 && cards>0)
    stat|=COMPLETEROW;

  if (p.size()==1 && (classes>0 || clubs>0))
    stat|=CLEARROW;

  while (cards > names && names>0) {
    int idx = -1;
    for (size_t k = 0; k<classified.size(); k++) {
      if (classified[k] == 3) {
        int a = idx>=0 ? _wtoi(p[idx]) : -1;
        if (a==-1)
          idx = k;
        else {
          int b = _wtoi(p[k]);
          if (b>0 && b<a)
            idx = k;
        }
      }
    }
    if (idx>=0) {
      classified[idx] = -2;
      cards--;
      unknown++;
    }
  }

  for (int k = p.size()-1; k>=0;k--) {
    if (classified[k] == -2) {
      classified.erase(classified.begin()+k);
      p.erase(p.begin()+k);
    }
  }
  return stat;
}


void oFreeImport::extractEntries(wchar_t *str, vector<oEntryBlock> &entries)
{
  entries.clear();

  vector< vector<wchar_t *> > parts;
  parts.reserve(wcslen(str)/24);
  set<int> lineTypes;
  int count=1;

  while(count>0) {
    wchar_t *line=extractLine(str, count);

    if (count>0) {
      parts.push_back( vector<wchar_t *>() );

      while(count>0) {
        wchar_t *out=extractPart(line, count);
        if (count>0)
          parts.back().push_back(out);
      }
      if ( analyzeHeaders(parts.back()) ) {
        parts.pop_back();
        count = 1;
      }
      else {
        lineTypes.insert(parts.back().size());
        count=1;
      }
    }

  }

  map<int, vector<MatchPattern> > patterns;
  map<int, int> nNames; //Number of name entries per pattern
  //Analyze line types
  for(set<int>::iterator it=lineTypes.begin();
                      it!=lineTypes.end();++it) {
    vector<MatchPattern> mp(*it);
    int limit=0;
    for (size_t k=0;k<parts.size();k++) {
      if (parts[k].size()==*it) {
        limit++;
        const vector<wchar_t *> &p=parts[k];
        for (int j=0;j<*it; j++) {
          if (classDB.lookup(p[j]))
            mp[j].nClass++;
          if (clubDB.lookup(p[j]))
            mp[j].nClub++;
          if (isName(p[j])) {
            mp[j].nName++;
            if (countWords(p[j])==1)
              mp[j].singleName++;
          }
          if (isCard(p[j]))
            mp[j].nCard++;
          if (isTime(p[j]))
            mp[j].nTime++;
        }
      }
    }

    if (limit==1)
      limit=0;
    else if (limit<=3)
      limit=1;
    else if (limit<=10)
      limit=2;
    else
      limit=limit/5;

    int nn=0;

    for (size_t k=0;k<mp.size();k++)
      if (mp[k].isName(limit))
        nn++;

    // Check that single names (Andersson, Karl) are neighbors.
    for (size_t k=0;k<mp.size();k++)
      if (mp[k].isName(limit) &&
          (k+1>=mp.size() || !mp[k+1].isName(limit)) &&
          (k==0 || !mp[k-1].isName(limit)) ) {
        mp[k].singleName=false;
      }

    nNames[*it]=nn;
    swap(patterns[*it], mp);
  }

  entries.reserve(parts.size());
  oEntryBlock entry(*this);

  vector<const char *> words;
  rulingClub = L"";
  rulingClass = L"";
  lastInsertedType = None;
  for (size_t k=0;k<parts.size();k++) {
    //Number of read names (add 2 for a complete name, 1 for a partial)
    int readNames=0;

    vector<wchar_t *> &p=parts[k];

    vector<int> classified;
    int res = preAnalyzeRow(p, patterns[p.size()], classified);

    int s = p.size();
    vector<MatchPattern> &ptrn=patterns[s];

    if (ptrn.empty())
      ptrn.resize(s);
    assert(ptrn.size() == s);

    lastInsertedType = None;
    if (res & CLEARROW) {
      entry.cleanEntry();
      if (entry.hasName()) {
        entries.push_back(entry);
      }
      entry.clear(rulingClub, rulingClass);
    }
    else if (res & COMPLETEROW) {
      entry.cleanEntry();
      if (entry.hasName()) {
        entries.push_back(entry);
      }
      entry.clear(rulingClub, rulingClass);
    }
    if (res & IGNOREROW)
      continue;

    for (int j=0;j<s;j++) {
      Types type = Unknown;
      if (ptrn[j].isClub() && clubDB.lookup(p[j]))
        type = Club;
      else if (ptrn[j].isClass() && classDB.lookup(p[j]))
        type = Class;
      else if (ptrn[j].isTime() && isTime(p[j]))
        type = Time;
      else if (ptrn[j].isCard() && isCard(p[j]))
        type = Card;
      else if (ptrn[j].isName() && isName(p[j]))
        type = Name;
      else if (clubDB.lookup(p[j]))
        type = Club;
      else if (classDB.lookup(p[j]))
        type = Class;
      else if (isTime(p[j]))
        type = Time;
      else if (isCard(p[j]))
        type = Card;
      else if (isName(p[j]) && entry.nameCount()<=1)
        type = Name;

      if (type==Unknown && ptrn.size()>1) {
        if (ptrn[j].isClub())
          type = Club;
        else if (ptrn[j].isClass())
          type = Class;
        else if (ptrn[j].isCard() && _wtoi(p[j])>0)
          type = Card;
      }

      //We have a suggested name, but there should only
      //be one name and we already got one.
      if ((type == Name && entry.hasName() && (2*nNames[s])<=readNames))
        type = Unknown;

      if (type==Name && isCrap(p[j]))
        type = Unknown;

      if (type == Unknown)
        analyzePart(p[j], ptrn[j], nNames[s], entry,
                      entries, (2*nNames[s])>readNames);
      else if (type == Club) {
        bool moreClubs = entry.acceptMoreClubs(getExpectedNumRunners(entry.eClass));
        if (!moreClubs && lastInsertedType == Club)
          continue;

        if (!moreClubs && entry.hasName()) {
          readNames=0;
          entry.cleanEntry();
          entries.push_back(entry);
          entry.clear(rulingClub, rulingClass);
        }
        else if (j<=1 && s<=2)
          // Set a ruling club if it first on a line containing at most 2 parts. (We allow club+class)
          rulingClub = p[j];

        lastInsertedType = Club;
        entry.setClub(p[j]);
        // If we have an odd number of names read
        // we were expecting a completing name,
        // but none came, so we complete what we have instead.
        if (readNames&1) {
          readNames++;
          entry.completeName();
        }
      }
      else if (type == Class) {
        if (lastInsertedType == Class)
          continue;

        if (entry.hasClass() && entry.hasName()) {
          readNames=0;
          entry.cleanEntry();
          entries.push_back(entry);
          entry.clear(rulingClub, rulingClass);
        }
        else if (j<=1 && s<=2)
          // Set a ruling class if it first on a line containing at most 2 parts. (We allow club+class)
          rulingClass = p[j];

        lastInsertedType = Class;
        entry.setClass(p[j]);
        if (readNames&1) {
          readNames++;
          entry.completeName();
        }
      }
      else if (type == Time) {
        if (lastInsertedType == Time)
          continue;

        if (!entry.eStartTime.empty()) {
          if (entry.ePersons.size()>1 && !entry.hasName())
            entry.ePersons.pop_back(); //Warning

          if (entry.hasName()) {
            readNames=0;
            entry.cleanEntry();
            entries.push_back(entry);
            entry.clear(rulingClub, rulingClass);
          }
        }
        lastInsertedType = Time;
        entry.setStartTime(p[j]);
        if (readNames&1) {
          readNames++;
          entry.completeName();
        }
      }
      else if (type == Card) {
        if (lastInsertedType == Card && !entry.needCard())
          continue;

        lastInsertedType = Card;
        entry.setCardNo(_wtoi(p[j]));
        if (readNames&1) {
          readNames++;
          entry.completeName();
        }
      }
      else if (type == Name) {
        lastInsertedType = Name;
        bool complete = ptrn[j].isCompleteName();
        entry.addPerson(p[j], complete);
        readNames += complete ? 2:1;
      }
    }// End loop over parts

    if (entry.hasName()) {
      entry.cleanEntry();
      entries.push_back(entry);
      entry.clear(rulingClub, rulingClass);
    }
    else if (res & COMPLETEROW) {
      entry.cleanEntry();
      if (entry.hasName())
        entries.push_back(entry); // Else lost data
      entry.clear(rulingClub, rulingClass);
    }
  }

  for (size_t k=1;k<entries.size(); k++) {
    if (entries[k].eClub.empty())
      entries[k].eClub=entries[k-1].eClub;

    if (entries[k].eClass.empty())
      entries[k].eClass=entries[k-1].eClass;
  }

  for (int k=int(entries.size())-2;k>=0; k--) {
    if (entries[k].eClub.empty())
      entries[k].eClub=entries[k+1].eClub;

    if (entries[k].eClass.empty())
      entries[k].eClass=entries[k+1].eClass;
  }
}

void oFreeImport::showEntries(gdioutput &gdi, const vector<oEntryBlock> &entries)
{
  gdi.setCX(20);
  for (size_t k=0;k<entries.size();k++) {
    gdi.pushX();
    int x=gdi.getCX();
    int y=gdi.getCY();
    gdi.addStringUT(y, x, 0, entries[k].eStartTime);
    vector<wstring> names = entries[k].getPersons();
    for (size_t j=0;j<names.size();j++) {
      gdi.addStringUT(y+gdi.getLineHeight()*j, x+50, 0, names[j]).setColor(colorRed);
      wstring clb = entries[k].getClub(j);
      gdi.addStringUT(y+gdi.getLineHeight()*j, x+300, 0, clb);
    }
    gdi.addStringUT(y, x+500, 0, entries[k].eClass);
    gdi.dropLine(0.1);
    gdi.popX();
  }
  gdi.refresh();
}

void oFreeImport::addEntries(pEvent oe, const vector<oEntryBlock> &entries)
{
  map<int, int> teamno; //For automatic name generation Club/Class
  for (size_t k=0;k<entries.size();k++) {
    assert(!entries[k].eClass.empty());
    pClass pc=oe->getClass(entries[k].eClass);

    if (!pc) {
      pc=oe->addClass(entries[k].eClass);
      if (entries[k].getNumPersons()>1)
        pc->setNumStages(entries[k].getNumPersons());
      pc->synchronize();
    }

    if (pc) {
      int nr=pc->getNumDistinctRunners();
      if (nr==1) {
        pRunner r=oe->addRunner(entries[k].getName(0),
                                entries[k].getClub(0), pc->getId(),
                                entries[k].getCard(0), 0, true);

        r->setStartTimeS(entries[k].eStartTime);
        r->setCardNo(entries[k].getCard(0), false);

        r->addClassDefaultFee(false);
        r->synchronize();
      }
      else {
        pClub club=oe->getClubCreate(0, entries[k].eClub);
        wstring team = entries[k].getTeamName();
        //int id=pc->getId() + 10000 * (club ? club->getId() : 0);

        //char tname[256];
        //sprintf_s(tname, "%s %d", entries[k].eClub.c_str(), ++teamno[id]);
        pTeam t=oe->addTeam(team, club ? club->getId() : 0, pc->getId());
        if (t) {
          t->setStartNo(t->getId(), false);

          for (int j=0;j<max(nr, entries[k].getNumPersons());j++) {
            pRunner r=oe->addRunner(entries[k].getName(j), entries[k].getClub(j),
                                    pc->getId(), entries[k].getCard(j), 0, false);
            r->setCardNo(entries[k].getCard(j), false);
            r->addClassDefaultFee(false);
            t->setRunner(j, r, true);
          }

          t->apply(true, 0, false);
        }
      }
    }
  }
  oe->makeUniqueTeamNames();
}

void oFreeImport::load()
{
  wchar_t bf[260];
  bool warn=false;
  getUserFile(bf, L"wfamily.mwd");
  try {
    familyDB.load(bf);
  } catch(std::exception &) {warn=true;}
  getUserFile(bf, L"wgiven.mwd");
  try {
    givenDB.load(bf);
  } catch(std::exception &) {warn=true;}
  getUserFile(bf, L"wclub.mwd");
  try {
    clubDB.load(bf);
  } catch(std::exception &) {warn=true;}
  getUserFile(bf, L"wclass.mwd");
  try {
    classDB.load(bf);
  } catch(std::exception &) {warn=true;}

  loaded=true;
}

#include "RunnerDB.h"

void oFreeImport::buildDatabases(oEvent &oe) {
  vector<wstring> given;
  vector<wstring> family;
  oe.getRunnerDatabase().getAllNames(given, family);

  for (size_t k = 0; k < given.size(); k++)
    givenDB.insert(given[k].c_str());

  for (size_t k = 0; k < given.size(); k++)
    familyDB.insert(family[k].c_str());

  const vector<oDBClubEntry> &clubs = oe.getRunnerDatabase().getClubDB(false);
  for (size_t k = 0; k< clubs.size(); k++) {
    clubDB.insert(clubs[k].getName().c_str());
    //clubs[k].altNames
  }

  //oe.init(*this);

  save();
}

void oEvent::init(oFreeImport &fi) {
  fi.init(Runners, Clubs, Classes);
}

void oFreeImport::save() const
{
  wchar_t bf[260];
  wstring bu;
  getUserFile(bf, L"wfamily.mwd");
  bu=wstring(bf)+L".old";
  _wremove(bu.c_str());
  _wrename(bf, bu.c_str());
  familyDB.save(bf);
  getUserFile(bf, L"wgiven.mwd");
  bu=wstring(bf)+L".old";
  _wremove(bu.c_str());
  _wrename(bf, bu.c_str());
  givenDB.save(bf);
  getUserFile(bf, L"wclub.mwd");
  bu=wstring(bf)+L".old";
  _wremove(bu.c_str());
  _wrename(bf, bu.c_str());
  clubDB.save(bf);
  getUserFile(bf, L"wclass.mwd");
  bu=wstring(bf)+L".old";
  _wremove(bu.c_str());
  _wrename(bf, bu.c_str());
  classDB.save(bf);
}
