/************************************************************************
    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 "oFreePunch.h"
#include "oEvent.h"
#include "Table.h"
#include "meos_util.h"
#include "Localizer.h"
#include "intkeymapimpl.hpp"
#include "socket.h"
#include "meosdb/sqltypes.h"
#include "gdioutput.h"

bool oFreePunch::disableHashing = false;

oFreePunch::oFreePunch(oEvent *poe, int card, int time, int type): oPunch(poe)
{
  Id=oe->getFreePunchId();
  CardNo = card;
  Time = time;
  Type = type;
  iHashType = 0;
  tRunnerId = 0;
}

oFreePunch::oFreePunch(oEvent *poe, int id): oPunch(poe)
{
  Id=id;
  oe->qFreePunchId = max(id, oe->qFreePunchId);
  iHashType = 0;
  tRunnerId = 0;
}

oFreePunch::~oFreePunch(void)
{
}

bool oFreePunch::Write(xmlparser &xml)
{
  if (Removed) return true;

  xml.startTag("Punch");
  xml.write("CardNo", CardNo);
  xml.write("Time", Time);
  xml.write("Type", Type);
  xml.write("Id", Id);
  xml.write("Updated", Modified.getStamp());
  xml.endTag();

  return true;
}

void oFreePunch::Set(const xmlobject *xo)
{
  xmlList xl;
  xo->getObjects(xl);

  xmlList::const_iterator it;

  for(it=xl.begin(); it != xl.end(); ++it){
    if (it->is("CardNo")){
      CardNo=it->getInt();
    }
    if (it->is("Type")){
      Type=it->getInt();
    }
    if (it->is("Time")){
      Time=it->getInt();
    }
    else if (it->is("Id")){
      Id=it->getInt();
    }
    else if (it->is("Updated")){
      Modified.setStamp(it->getRaw());
    }
  }
}

bool oFreePunch::setCardNo(int cno, bool databaseUpdate) {
  if (cno != CardNo) {
    pRunner r1 = oe->getRunner(tRunnerId, 0);
    int oldControlId = tMatchControlId;
    //oe->punchIndex[itype].remove(CardNo); // Remove from index
    // Remove ourself from index
    oEvent::PunchIndexType &pi = oe->punchIndex[iHashType];
    oEvent::PunchConstIterator it = pi.find(CardNo);
    while (it != pi.end() && it->first == CardNo) {
      if (it->second == this) {
        pi.erase(it);
        break;
      }
      ++it;
    }
    oe->removeFromPunchHash(CardNo, Type, Time);
    rehashPunches(*oe, CardNo, 0);

    CardNo = cno;
    oe->insertIntoPunchHash(CardNo, Type, Time);

    rehashPunches(*oe, CardNo, this);
    pRunner r2 = oe->getRunner(tRunnerId, 0);

    if (r1 && oldControlId > 0)
      r1->markClassChanged(oldControlId);
    if (r2 && iHashType > 0)
      r2->markClassChanged(tMatchControlId);

    if (!databaseUpdate)
      updateChanged();

    return true;
  }
  return false;
}

void oFreePunch::remove()
{
  if (oe)
    oe->removeFreePunch(Id);
}

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

Table *oEvent::getPunchesTB()//Table mode
{
  if (tables.count("punch") == 0) {
    Table *table=new Table(this, 20, L"Stämplingar", "punches");
    table->addColumn("Id", 70, true, true);
    table->addColumn("Ändrad", 150, false);
    table->addColumn("Bricka", 70, true);
    table->addColumn("Kontroll", 70, true);
    table->addColumn("Tid", 70, false);
    table->addColumn("Löpare", 170, false);
    table->addColumn("Lag", 170, false);
    tables["punch"] = table;
    table->addOwnership();
  }

  tables["punch"]->update();
  return tables["punch"];
}

void oEvent::generatePunchTableData(Table &table, oFreePunch *addPunch)
{
  if (addPunch) {
    addPunch->addTableRow(table);
    return;
  }

  synchronizeList(oLPunchId);
  oFreePunchList::iterator it;
  oe->setupCardHash(false);
  table.reserve(punches.size());
  for (it = punches.begin(); it != punches.end(); ++it){
    if (!it->isRemoved()){
      it->addTableRow(table);
    }
  }
  oe->setupCardHash(true);
}

void oFreePunch::addTableRow(Table &table) const {
  oFreePunch &it = *pFreePunch(this);
  table.addRow(getId(), &it);
  int row = 0;
  table.set(row++, it, TID_ID, itow(getId()), false, cellEdit);
  table.set(row++, it, TID_MODIFIED, getTimeStamp(), false, cellEdit);
  table.set(row++, it, TID_CARD, itow(getCardNo()), true, cellEdit);
  table.set(row++, it, TID_CONTROL, getType(), true, cellEdit);
  table.set(row++, it, TID_TIME, getTime(), true, cellEdit);
  pRunner r = 0;
  if (CardNo > 0)
    r = oe->getRunnerByCardNo(CardNo, Time, false);

  table.set(row++, it, TID_RUNNER, r ? r->getName() : L"?", false, cellEdit);

  if (r && r->getTeam())
    table.set(row++, it, TID_TEAM, r->getTeam()->getName(), false, cellEdit);
  else
    table.set(row++, it, TID_TEAM, L"", false, cellEdit);
}

bool oFreePunch::inputData(int id, const wstring &input,
                           int inputId, wstring &output, bool noUpdate)
{
  synchronize(false);
  switch(id) {
    case TID_CARD:
      setCardNo(_wtoi(input.c_str()));
      synchronize(true);
      output = itow(CardNo);
      break;

    case TID_TIME:
      setTime(input);
      synchronize(true);
      output = getTime();
      break;

    case TID_CONTROL:
      setType(input);
      synchronize(true);
      output = getType();
  }
  return false;
}

void oFreePunch::fillInput(int id, vector< pair<wstring, size_t> > &out, size_t &selected)
{
}

void oFreePunch::setTimeInt(int t, bool databaseUpdate) {
  if (t != Time) {
    oe->removeFromPunchHash(CardNo, Type, Time);
    Time = t;
    oe->insertIntoPunchHash(CardNo, Type, Time);
    rehashPunches(*oe, CardNo, 0);
    if (!databaseUpdate)
      updateChanged();
  }
}

bool oFreePunch::setType(const wstring &t, bool databaseUpdate) {
  int type = _wtoi(t.c_str());
  int ttype = 0;
  if (type>0 && type<10000)
    ttype = type;
  else {
    if (t == lang.tl("Check"))
      ttype = oPunch::PunchCheck;
    else if (t == lang.tl("Mål"))
      ttype = oPunch::PunchFinish;
    if (t == lang.tl("Start"))
      ttype = oPunch::PunchStart;
  }
  if (ttype > 0 && ttype != Type) {
    oe->removeFromPunchHash(CardNo, Type, Time);
    Type = ttype;
    oe->insertIntoPunchHash(CardNo, Type, Time);
    int oldControlId = tMatchControlId;
    rehashPunches(*oe, CardNo, 0);

    pRunner r = oe->getRunner(tRunnerId, 0);

    if (r) {
      r->markClassChanged(tMatchControlId);
      if (oldControlId > 0)
        r->markClassChanged(oldControlId);
    }

    if (!databaseUpdate)
      updateChanged();

    return true;
  }
  return false;
}

void oFreePunch::rehashPunches(oEvent &oe, int cardNo, pFreePunch newPunch) {
  if (disableHashing || (cardNo == 0 && !oe.punchIndex.empty()) || oe.punches.empty())
    return;
  vector<pFreePunch> fp;

  if (oe.punchIndex.empty()) {
    // Rehash all punches. Ignore cardNo and newPunch (will be included automatically)
    fp.reserve(oe.punches.size());
    for (oFreePunchList::iterator pit = oe.punches.begin(); pit != oe.punches.end(); ++pit) {
      if (pit->isRemoved())
        continue;
      fp.push_back(&(*pit));
    }

    sort(fp.begin(), fp.end(), FreePunchComp());
    disableHashing = true;
    try {
      for (size_t j = 0; j < fp.size(); j++) {
        pFreePunch punch = fp[j];
        punch->iHashType = oe.getControlIdFromPunch(punch->Time, punch->Type, punch->CardNo, true,
                                                    *punch);

        oEvent::PunchIndexType &card2Punch = oe.punchIndex[punch->iHashType];
        card2Punch.insert(make_pair(punch->CardNo, punch));
      }
    }
    catch(...) {
      disableHashing = false;
      throw;
    }
    disableHashing = false;
    return;
  }

  map<int, oEvent::PunchIndexType>::iterator it;
  fp.reserve(oe.punchIndex.size() + 1);

  // Get all punches for the specified card.
  for(it = oe.punchIndex.begin(); it != oe.punchIndex.end(); ++it) {
    pair<oEvent::PunchConstIterator, oEvent::PunchConstIterator> res = it->second.equal_range(cardNo);
    oEvent::PunchConstIterator pIter = res.first;
    while(pIter != res.second) {
      pFreePunch punch = pIter->second;
      assert(punch && punch->CardNo == cardNo);
      if (!punch->isRemoved()) {
        fp.push_back(punch);
      }
      ++pIter;
    }
    it->second.erase(res.first, res.second);
  }

  if (newPunch)
    fp.push_back(newPunch);

  sort(fp.begin(), fp.end(), FreePunchComp());
  for (size_t j = 0; j < fp.size(); j++) {
    if (j>0 && fp[j-1] == fp[j])
      continue; //Skip duplicates
    pFreePunch punch = fp[j];
    punch->iHashType = oe.getControlIdFromPunch(punch->Time, punch->Type, cardNo, true, *punch);
    oEvent::PunchIndexType &card2Punch = oe.punchIndex[punch->iHashType];
    card2Punch.insert(make_pair(punch->CardNo, punch));
  }
}

//const int legHashConstant = 100000;
int oFreePunch::getControlHash(int courseControlId, int race) {
  int newId = courseControlId + race*100000000;
  return newId;
}

int oFreePunch::getControlIdFromHash(int hash, bool courseControlId) {
  int r = (hash%100000000);
  if (courseControlId)
    return r;
  else
    return oControl::getIdIndexFromCourseControlId(r).first;
}

int oEvent::getControlIdFromPunch(int time, int type, int card,
                                  bool markClassChanged, oFreePunch &punch) {
  pRunner r = getRunnerByCardNo(card, time);
  punch.tRunnerId = -1;
  punch.tMatchControlId = type;
  if (r!=0) {
    punch.tRunnerId = r->Id;
  }
  int race = 0;
  if (type!=oPunch::PunchFinish) {
    pCourse c = r ? r->getCourse(false): 0;

    if (c!=0) {
      race = r->getRaceNo();
      for (int k=0; k<c->nControls; k++) {
        pControl ctrl=c->getControl(k);
        if (ctrl && ctrl->hasNumber(type)) {
          int courseControlId = c->getCourseControlId(k);
          pFreePunch p = getPunch(r->getId(), courseControlId, card);
          if (!p || (p && abs(p->Time-time)<60)) {
            ctrl->tHasFreePunchLabel = true;
            punch.tMatchControlId = ctrl->getId();
            punch.tIndex = k;
            int newId = oFreePunch::getControlHash(courseControlId, race);
            if (newId != punch.iHashType && markClassChanged && r) {
              r->markClassChanged(ctrl->getId());
              if (punch.iHashType > 0)
                r->markClassChanged(oFreePunch::getControlIdFromHash(punch.iHashType, false));
            }

            //Code controlId and runner race number into code
            return newId;
          }
        }
      }
    }
  }

  int newId = oFreePunch::getControlHash(type, 0);

  if (newId != punch.iHashType && markClassChanged && r) {
    r->markClassChanged(type);
    if (punch.iHashType > 0)
      r->markClassChanged(oFreePunch::getControlIdFromHash(punch.iHashType, false));
  }

  return newId;
}

void oEvent::getFreeControls(set<int> &controlId) const
{
  controlId.clear();
  for (map<int, PunchIndexType >::const_iterator it = punchIndex.begin(); it != punchIndex.end(); ++it) {
    int id = oFreePunch::getControlIdFromHash(it->first, false);
    controlId.insert(id);
  }
}

//set< pair<int,int> > readPunchHash;

void oEvent::insertIntoPunchHash(int card, int code, int time) {
  if (time > 0) {
    int p1 = time * 4096 + code;
    int p2 = card;
    readPunchHash.insert(make_pair(p1, p2));
  }
}

void oEvent::removeFromPunchHash(int card, int code, int time) {
  int p1 = time * 4096 + code;
  int p2 = card;
  readPunchHash.erase(make_pair(p1, p2));
}

bool oEvent::isInPunchHash(int card, int code, int time) {
  int p1 = time * 4096 + code;
  int p2 = card;
  return readPunchHash.count(make_pair(p1, p2)) > 0;
}


pFreePunch oEvent::addFreePunch(int time, int type, int card, bool updateStartFinish) {
  if (time > 0 && isInPunchHash(card, type, time))
    return 0;
  oFreePunch ofp(this, card, time, type);

  punches.push_back(ofp);
  pFreePunch fp=&punches.back();
  oFreePunch::rehashPunches(*this, card, fp);
  insertIntoPunchHash(card, type, time);

  if (fp->getTiedRunner() && oe->isClient() && oe->getPropertyInt("UseDirectSocket", true)!=0) {
    SocketPunchInfo pi;
    pi.runnerId = fp->getTiedRunner()->getId();
    pi.time = fp->getAdjustedTime();
    pi.status = fp->getTiedRunner()->getStatus();
    if (fp->getTypeCode() > 10)
      pi.iHashType = fp->getIHashType();
    else
      pi.iHashType = fp->getTypeCode();

    getDirectSocket().sendPunch(pi);
  }

  fp->updateChanged();
  fp->synchronize();

  // Update start/finish time
  if (updateStartFinish && type == oPunch::PunchStart || type == oPunch::PunchFinish) {
    pRunner tr = fp->getTiedRunner();
    if (tr && tr->getStatus() == StatusUnknown && time > 0) {
      tr->synchronize();
      if (type == oPunch::PunchStart) {
        if (tr->getClassRef(false) && !tr->getClassRef(true)->ignoreStartPunch())
          tr->setStartTime(time, true, false);
      }
      else
        tr->setFinishTime(time);

      // Direct result
      if (type == oPunch::PunchFinish && tr->getClassRef(false) && tr->getClassRef(true)->hasDirectResult()) {
        if (tr->getCourse(false) == 0 && tr->getCard() == 0) {
          tr->setStatus(StatusOK, true, false, true);
        }
        else if (tr->getCourse(false) != 0 && tr->getCard() == 0) {
          pCard card = allocateCard(tr);
          card->setupFromRadioPunches(*tr);
          vector<int> mp;
          card->synchronize();
          tr->addPunches(card, mp);
        }
      }

      tr->synchronize(true);
    }
  }
  if (fp->getTiedRunner())
    pushDirectChange();
  return fp;
}

pFreePunch oEvent::addFreePunch(oFreePunch &fp) {
  insertIntoPunchHash(fp.CardNo, fp.Type, fp.Time);
  punches.push_back(fp);
  pFreePunch fpz=&punches.back();
  oFreePunch::rehashPunches(*this, fp.CardNo, fpz);

  if (!fpz->existInDB() && HasDBConnection) {
    fpz->changed = true;
    fpz->synchronize();
  }
  return fpz;
}

void oEvent::removeFreePunch(int Id) {
  oFreePunchList::iterator it;

  for (it=punches.begin(); it != punches.end(); ++it) {
    if (it->Id==Id) {
      pRunner r = getRunner(it->tRunnerId, 0);
      if (r && r->Class) {
        r->markClassChanged(it->tMatchControlId);
        classChanged(r->Class, true);
      }
      pFreePunch fp = &*it;
      if (HasDBConnection)
        msRemove(fp);
      //punchIndex[it->itype].remove(it->CardNo);
      PunchIndexType &ix = punchIndex[it->iHashType];
      pair<PunchConstIterator, PunchConstIterator> res = ix.equal_range(it->CardNo);
      while (res.first != res.second) {
        if (res.first->second == fp) {
          PunchConstIterator rm = res.first;
          ++res.first;
          ix.erase(rm);
        }
        else
          ++res.first;
      }

      int cardNo = fp->CardNo;
      removeFromPunchHash(cardNo, fp->Type, fp->Time);
      punches.erase(it);
      oFreePunch::rehashPunches(*this, cardNo, 0);
      dataRevision++;
      return;
    }
  }
}

pFreePunch oEvent::getPunch(int Id) const
{
  oFreePunchList::const_iterator it;

  for (it=punches.begin(); it != punches.end(); ++it) {
    if (it->Id==Id) {
      if (it->isRemoved())
        return 0;
      return const_cast<pFreePunch>(&*it);
    }
  }
  return 0;
}

pFreePunch oEvent::getPunch(int runnerId, int courseControlId, int card) const
{
  //Lazy setup
  oFreePunch::rehashPunches(*oe, 0, 0);

  pRunner r = oe->getRunner(runnerId, 0);
  int runnerRace = r ? r->getRaceNo() : 0;
  map<int, oEvent::PunchIndexType>::const_iterator it1;

  int itype = oFreePunch::getControlHash(courseControlId, runnerRace);

  it1=punchIndex.find(itype);

  if (it1!=punchIndex.end()) {
    const oEvent::PunchIndexType &cIndex = it1->second;
    pair<oEvent::PunchConstIterator, oEvent::PunchConstIterator> res = cIndex.equal_range(card);
    oEvent::PunchConstIterator pIter = res.first;
    while(pIter != res.second) {
      pFreePunch punch = pIter->second;
      if (!punch->isRemoved()) {
        assert(punch && punch->CardNo == card);
        if (punch->tRunnerId == runnerId || runnerId == 0)
          return punch;
        ++pIter;
      }
    }
  }

  map<pair<int, int>, oFreePunch>::const_iterator res = advanceInformationPunches.find(make_pair(itype, card));
  if (res != advanceInformationPunches.end())
    return (pFreePunch)&res->second;

  return 0;
}

void oEvent::getPunchesForRunner(int runnerId, vector<pFreePunch> &runnerPunches) const {
  runnerPunches.clear();
  pRunner r = getRunner(runnerId, 0);
  if (r == 0)
    return;

  // Get times for when other runners used this card
  vector< pair<int, int> > times;

  for (oRunnerList::const_iterator it = Runners.begin(); it != Runners.end(); ++it) {
    if (it->Id == runnerId)
      continue;
    if (it->Card && it->CardNo == r->CardNo) {
      pair<int, int> t = it->Card->getTimeRange();
      if (it->getStartTime() > 0)
        t.first = min(it->getStartTime(), t.first);

      if (it->getFinishTime() > 0)
        t.second = max(it->getFinishTime(), t.second);

      times.push_back(t);
    }
  }

  for (oFreePunchList::const_iterator it = punches.begin(); it != punches.end(); ++it) {
    if (it->CardNo == r->CardNo) {
      bool other = false;
      int t = it->Time;
      for (size_t k = 0; k<times.size(); k++) {
        if (t >= times[k].first && t <= times[k].second)
          other = true;
      }

      if (!other)
        runnerPunches.push_back(pFreePunch(&*it));
    }
  }

  // XXX Advance punches...
}


bool oEvent::advancePunchInformation(const vector<gdioutput *> &gdi, vector<SocketPunchInfo> &pi,
                                     bool fetchPunch, bool fetchFinish) {
  if (pi.empty())
    return false;

  bool m = false;
  for (size_t k = 0; k < pi.size(); k++) {
    pRunner r = getRunner(pi[k].runnerId, 0);
    if (!r)
      continue;
    if (pi[k].iHashType == oPunch::PunchFinish && fetchFinish) {
      if (r->getStatus() == StatusUnknown && r->getFinishTime() <= 0 && !r->isChanged()) {
        r->FinishTime = pi[k].time;
        r->tStatus = RunnerStatus(pi[k].status);
        r->status = RunnerStatus(pi[k].status); // Will be overwritten (do not set isChanged flag)
        if (r->Class) {
          r->markClassChanged(oPunch::PunchFinish);
          classChanged(r->Class, false);
        }
        m = true;
      }
    }
    else if (fetchPunch) {
      // controlId is already the encoded format including index and race
      if (getPunch(pi[k].runnerId, pi[k].iHashType, r->getCardNo()) == 0) {
        oFreePunch fp(this, 0, pi[k].time, pi[k].iHashType);
        fp.tRunnerId = pi[k].runnerId;
        fp.iHashType = pi[k].iHashType;
        fp.tIndex = 0;
        fp.tMatchControlId = oFreePunch::getControlIdFromHash(fp.iHashType, false);
        fp.changed = false;
		pair<int, int> hc(pi[k].iHashType, r->getCardNo());
        advanceInformationPunches.insert(make_pair(hc,fp));
        if (r->Class) {
          r->markClassChanged(oFreePunch::getControlIdFromHash(pi[k].iHashType, false));
          classChanged(r->Class, true);
        }
        m = true;
      }
    }
  }
  if (m) {
    dataRevision++;
    for (size_t k = 0; k<gdi.size(); k++) {
      if (gdi[k])
        gdi[k]->makeEvent("DataUpdate", "autosync", 0, 0, false);
    }
  }
  return m;
}

void oEvent::getLatestPunches(int firstTime, vector<const oFreePunch *> &punchesOut) const {
  for (map< pair<int, int>, oFreePunch>::const_iterator it = advanceInformationPunches.begin();
       it != advanceInformationPunches.end(); ++it) {
    int time = it->second.getModificationTime();
    if (time >= firstTime)
      punchesOut.push_back(&it->second);
  }

  for (oFreePunchList::const_iterator it = punches.begin(); it != punches.end(); ++it) {
    int time = it->getModificationTime();
    if (time >= firstTime)
      punchesOut.push_back(&*it);
  }
}

pRunner oFreePunch::getTiedRunner() const {
  return oe->getRunner(tRunnerId, 0);
}

void oFreePunch::changedObject() {
  pRunner r = getTiedRunner();
  if (r && tMatchControlId>0)
    r->markClassChanged(tMatchControlId);
}
