/************************************************************************
    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

************************************************************************/

// oCard.cpp: implementation of the oCard class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "oCard.h"
#include "oEvent.h"
#include "gdioutput.h"
#include "table.h"
#include "Localizer.h"
#include "meos_util.h"

#include <algorithm>
#include <cassert>

#include "SportIdent.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

oCard::oCard(oEvent *poe): oBase(poe)
{
  Id=oe->getFreeCardId();
  cardNo=0;
  readId=0;
  tOwner=0;
}

oCard::oCard(oEvent *poe, int id): oBase(poe)
{
  Id=id;
  cardNo=0;
  readId=0;
  tOwner=0;
  oe->qFreeCardId = max(id, oe->qFreeCardId);
}

oCard::~oCard()
{

}

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

  xml.startTag("Card");
  xml.write("CardNo", cardNo);
  xml.write("Punches", getPunchString());
  xml.write("ReadId", readId);
  xml.write("Id", Id);
  xml.write("Updated", Modified.getStamp());
  xml.endTag();

  return true;
}

void oCard::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();
    }
    else if (it->is("Punches")){
      importPunches(it->getRaw());
    }
    else if (it->is("ReadId")){
      readId = it->getInt();
    }
    else if (it->is("Id")){
      Id = it->getInt();
    }
    else if (it->is("Updated")){
      Modified.setStamp(it->getRaw());
    }
  }
}

void oCard::setCardNo(int c)
{
  if (cardNo!=c)
    updateChanged();

  cardNo=c;
}

const wstring &oCard::getCardNoString() const {
  return itow(cardNo);
}

void oCard::addPunch(int type, int time, int matchControlId)
{
  oPunch p(oe);
  p.Time = time;
  p.Type = type;
  p.tMatchControlId = matchControlId;
  p.isUsed =  matchControlId!=0;

  if (punches.empty())
    punches.push_back(p);
  else {
    oPunch oldBack = punches.back();
    if (oldBack.isFinish()) { //Make sure finish is last.
      punches.pop_back();
      punches.push_back(p);
      punches.push_back(oldBack);
    }
    else
      punches.push_back(p);
  }
  updateChanged();
}

string oCard::getPunchString()
{
  oPunchList::iterator it;

  string pstring;

  for(it=punches.begin(); it != punches.end(); ++it){
    pstring += it->codeString();
  }

  return pstring;
}

void oCard::importPunches(const string &s) {
  int startpos=0;
  int endpos;

  endpos=s.find_first_of(';', startpos);
  punches.clear();

  while(endpos!=string::npos) {
    oPunch p(oe);
    p.decodeString(s.substr(startpos, endpos));
    punches.push_back(p);
    startpos=endpos+1;
    endpos=s.find_first_of(';', startpos);
  }
  return;
}

bool oCard::fillPunches(gdioutput &gdi, const string &name, pCourse crs) {
  oPunchList::iterator it;
  synchronize(true);
  int ix = 0;
  for (it=punches.begin(); it != punches.end(); ++it) {
    it->tCardIndex = ix++;
  }

  gdi.clearList(name);

  bool showStart = crs ? !crs->useFirstAsStart() : true;
  bool showFinish = crs ? !crs->useLastAsFinish() : true;

  bool hasStart=false;
  bool hasFinish=false;
  bool extra=false;
  int k=0;

  pControl ctrl=0;

  int matchPunch=0;
  int punchRemain=1;
  bool hasRogaining = false;
  if (crs) {
    ctrl=crs->getControl(matchPunch);
    hasRogaining = crs->hasRogaining();
  }
  if (ctrl)
    punchRemain=ctrl->getNumMulti();

  map<int, pair<int, pPunch > > rogainingIndex;
  
  if (crs) {
    for (it=punches.begin(); it != punches.end(); ++it) {
      if (it->tRogainingIndex >= 0) {
        rogainingIndex[it->tRogainingIndex] = make_pair(it->tCardIndex, &*it);
      }
      ix++;
    }
  }

  for (it=punches.begin(); it != punches.end(); ++it){
    if (!hasStart && !it->isStart()){
      if (it->isUsed){
        if (showStart)
          gdi.addItem(name, lang.tl("Start")+L"\t-", -1);
        hasStart=true;
      }
    }

    if (crs && it->tRogainingIndex != -1)
      continue;

    {
      if (it->isStart())
        hasStart=true;
      else if (crs && it->isUsed && !it->isFinish() &&  !it->isCheck()) {
        while(ctrl && it->tMatchControlId!=ctrl->getId()) {
          if (ctrl->isRogaining(hasRogaining)) {
            if (rogainingIndex.count(matchPunch) == 1)
              gdi.addItem(name, rogainingIndex[matchPunch].second->getString(),
                                rogainingIndex[matchPunch].first);
            else
              gdi.addItem(name, L"-\t-", -1);
          }
          else {
            while(0<punchRemain--) {
              gdi.addItem(name, L"-\t-", -1);
            }
          }
          // Next control
          ctrl=crs ? crs->getControl(++matchPunch):0;
          punchRemain=ctrl ? ctrl->getNumMulti() : 1;
        }
      }


      if ((!crs || it->isUsed) || (showFinish && it->isFinish()) || (showStart && it->isStart())) {
        if (it->isFinish() && hasRogaining && crs) {
          while (ctrl) {
            if (ctrl->isRogaining(hasRogaining)) {
              // Check if we have reach finihs without adding rogaining punches
              while (ctrl && ctrl->isRogaining(hasRogaining)) {
                if (rogainingIndex.count(matchPunch) == 1)
                  gdi.addItem(name, rogainingIndex[matchPunch].second->getString(),
                                    rogainingIndex[matchPunch].first);
                else
                  gdi.addItem(name, L"-\t-", -1);
                ctrl = crs->getControl(++matchPunch);
              }
              punchRemain = ctrl ? ctrl->getNumMulti() : 1;
            }
            else {
              gdi.addItem(name, L"-\t-", -1);
              ctrl = crs->getControl(++matchPunch);
            }
          }
        }

        if (it->isFinish() && crs) { //Add missing punches before the finish
          while(ctrl) {
            gdi.addItem(name, L"-\t-", -1);
            ctrl = crs->getControl(++matchPunch);
          }
        }

        gdi.addItem(name, it->getString(), it->tCardIndex);

        if (!(it->isFinish() || it->isStart())) {
          punchRemain--;
          if (punchRemain<=0) {
            // Next contol
            ctrl = crs ? crs->getControl(++matchPunch):0;

            // Match rogaining here
            while (ctrl && ctrl->isRogaining(hasRogaining)) {
              if (rogainingIndex.count(matchPunch) == 1)
                gdi.addItem(name, rogainingIndex[matchPunch].second->getString(),
                                  rogainingIndex[matchPunch].first);
              else
                gdi.addItem(name, L"-\t-", -1);
              ctrl = crs->getControl(++matchPunch);
            }
            punchRemain = ctrl ? ctrl->getNumMulti() : 1;
          }
        }
      }
      else
        extra=true;

      k++;

      if (it->isFinish() && showFinish)
        hasFinish=true;
    }
  }

  if (!hasStart && showStart)
    gdi.addItem(name, lang.tl("Start")+L"\t-", -1);

  if (!hasFinish && showFinish) {

    while (ctrl) {
      if (ctrl->isRogaining(hasRogaining)) {
        // Check if we have reach finihs without adding rogaining punches
        while (ctrl && ctrl->isRogaining(hasRogaining)) {
          if (rogainingIndex.count(matchPunch) == 1)
            gdi.addItem(name, rogainingIndex[matchPunch].second->getString(),
                              rogainingIndex[matchPunch].first);
          else
            gdi.addItem(name, L"-\t-", -1);
          ctrl = crs->getControl(++matchPunch);
        }
        punchRemain = ctrl ? ctrl->getNumMulti() : 1;
      }
      else {
        gdi.addItem(name, L"-\t-", -1);
        ctrl = crs->getControl(++matchPunch);
      }
    }

    gdi.addItem(name, lang.tl("Mål")+L"\t-", -1);
  }

  if (extra) {
    //Show punches that are not used.
    k=0;
    gdi.addItem(name, L"", -1);
    gdi.addItem(name, lang.tl("Extra stämplingar"), -1);
    for (it=punches.begin(); it != punches.end(); ++it) {
      if (!it->isUsed && !(it->isFinish() && showFinish) && !(it->isStart() && showStart))
        gdi.addItem(name, it->getString(), it->tCardIndex);
    }
  }
  return true;
}


void oCard::insertPunchAfter(int pos, int type, int time)
{
  if (pos==1023)
    return;

  oPunchList::iterator it;

  oPunch punch(oe);
  punch.Time=time;
  punch.Type=type;

  int k=-1;
  for (it=punches.begin(); it != punches.end(); ++it) {
    if (k==pos) {
      updateChanged();
      punches.insert(it, punch);
      return;
    }
    k++;
  }

  updateChanged();
  //Insert last
  punches.push_back(punch);
}

void oCard::deletePunch(pPunch pp)
{
  if (pp == 0)
    throw std::exception("Punch not found");
  int k=0;
  oPunchList::iterator it;

  for (it=punches.begin(); it != punches.end(); ++it) {
    if (&*it == pp) {
      punches.erase(it);
      updateChanged();
      return;
    }
    k++;
  }
}

wstring oCard::getInfo() const
{
  wchar_t bf[128];
  swprintf_s(bf, lang.tl("Löparbricka %d").c_str(), cardNo);
  return bf;
}

oPunch *oCard::getPunch(const pPunch punch)
{
  int k=0;
  oPunchList::iterator it;

  for (it=punches.begin(); it != punches.end(); ++it) {
    if (&*it == punch) return &*it;
    k++;
  }
  return 0;
}


oPunch *oCard::getPunchByType(int Type) const
{
  oPunchList::const_iterator it;

  for (it=punches.begin(); it != punches.end(); ++it)
    if (it->Type==Type)
      return pPunch(&*it);

  return 0;
}

oPunch *oCard::getPunchById(int courseControlId) const
{
  pair<int, int> idix = oControl::getIdIndexFromCourseControlId(courseControlId);
  oPunchList::const_iterator it;
  pPunch res = 0;
  for (it=punches.begin(); it != punches.end(); ++it) {
    if (it->tMatchControlId==idix.first) {
      res = pPunch(&*it);
      if (idix.second == 0)
        return res;
      --idix.second; // Not this match, decrease count
    }
  }
  return 0; // Punch not found
}


oPunch *oCard::getPunchByIndex(int ix) const
{
  oPunchList::const_iterator it;
  for (it=punches.begin(); it != punches.end(); ++it) {
    if (0 == ix--)
      return pPunch(&*it);
  }
  return 0;
}


void oCard::setReadId(const SICard &card)
{
  updateChanged();
  readId=card.calculateHash();
}

bool oCard::isCardRead(const SICard &card) const
{
  if (readId==card.calculateHash())
    return true;
  else return false;
}

void oCard::getSICard(SICard &card) const {
  card.clear(0);
  card.CardNumber = cardNo;
  oPunchList::const_iterator it;
  for (it = punches.begin(); it != punches.end(); ++it) {
    if (it->Type>30)
      card.Punch[card.nPunch++].Code = it->Type;
  }
}


pRunner oCard::getOwner() const {
  return tOwner && !tOwner->isRemoved() ? tOwner : 0;
}

bool oCard::setPunchTime(const pPunch punch, const wstring &time)
{
  oPunch *op=getPunch(punch);
  if (!op) return false;

  DWORD ot=op->Time;
  op->setTime(time);

  if (ot!=op->Time)
    updateChanged();

  return true;
}


pCard oEvent::getCard(int Id) const
{
  if (Id < int(Cards.size() / 2)) {
    for (oCardList::const_iterator it = Cards.begin(); it != Cards.end(); ++it){
      if (it->Id==Id)
        return const_cast<pCard>(&*it);
    }
  }
  else {
    for (oCardList::const_reverse_iterator it = Cards.rbegin(); it != Cards.rend(); ++it){
      if (it->Id==Id)
        return const_cast<pCard>(&*it);
    }
  }
  return 0;
}

void oEvent::getCards(vector<pCard> &c) {
  synchronizeList(oLCardId);
  c.clear();
  c.reserve(Cards.size());

  for (oCardList::iterator it = Cards.begin(); it != Cards.end(); ++it) {
    if (!it->isRemoved())
     c.push_back(&*it);
  }
}


pCard oEvent::addCard(const oCard &oc)
{
  if (oc.Id<=0)
    return 0;

  Cards.push_back(oc);

  return &Cards.back();
}


pCard oEvent::getCardByNumber(int cno) const
{
  oCardList::const_reverse_iterator it;
  pCard second = 0;
  for (it=Cards.rbegin(); it != Cards.rend(); ++it){
    if (it->cardNo==cno) {
      if (it->getOwner() == 0)
        return const_cast<pCard>(&*it);
      else if (second == 0)
        second = const_cast<pCard>(&*it);
    }
  }
  return second;
}

bool oEvent::isCardRead(const SICard &card) const
{
  oCardList::const_iterator it;

  for(it=Cards.begin(); it!=Cards.end(); ++it) {
    if (it->isRemoved())
      continue;

    if (it->cardNo==card.CardNumber && it->isCardRead(card))
      return true;
  }

  return false;
}


Table *oEvent::getCardsTB() //Table mode
{
  oCardList::iterator it;

  Table *table=new Table(this, 20, L"Brickor", "cards");

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

  table->addColumn("Bricka", 120, true);
  table->addColumn("Deltagare", 200, false);

  table->addColumn("Starttid", 70, false);
  table->addColumn("Måltid", 70, false);
  table->addColumn("Stämplingar", 70, true);

  table->setTableProp(Table::CAN_DELETE);
  table->update();

  return table;
}

void oEvent::generateCardTableData(Table &table, oCard *addCard)
{
  if (addCard) {
    addCard->addTableRow(table);
    return;
  }

  oCardList::iterator it;
  synchronizeList(oLCardId, true, false);
  synchronizeList(oLRunnerId, false, true);

  for (it=Cards.begin(); it!=Cards.end(); ++it) {
    if (!it->isRemoved()) {
      it->addTableRow(table);
    }
  }
}

void oCard::addTableRow(Table &table) const {

  wstring runner(lang.tl("Oparad"));
  if (getOwner())
    runner = tOwner->getNameAndRace(true);

  oCard &it = *pCard(this);
  table.addRow(getId(), &it);

  int row = 0;
  table.set(row++, it, TID_ID, itow(getId()), false);
  table.set(row++, it, TID_MODIFIED, getTimeStamp(), false);

  table.set(row++, it, TID_CARD, getCardNoString(), true, cellAction);

  table.set(row++, it, TID_RUNNER, runner, true, cellAction);

  oPunch *p=getPunchByType(oPunch::PunchStart);
  wstring time;
  if (p)
    time = p->getTime();
  else
    time = makeDash(L"-");
  table.set(row++, it, TID_START, time, false, cellEdit);

  p = getPunchByType(oPunch::PunchFinish);
  if (p)
    time = p->getTime();
  else
    time = makeDash(L"-");
  
  table.set(row++, it, TID_FINISH, time, false, cellEdit);

  table.set(row++, it, TID_COURSE, itow(getNumPunches()), false, cellEdit);
}

oDataContainer &oCard::getDataBuffers(pvoid &data, pvoid &olddata, pvectorstr &strData) const {
  throw std::exception("Unsupported");
}

int oCard::getSplitTime(int startTime, const pPunch punch) const {

  for (oPunchList::const_iterator it = punches.begin(); it != punches.end(); ++it) {
    if (&*it == punch) {
      int t = it->getAdjustedTime();
      if (t<=0)
        return -1;

      if (startTime > 0)
        return t - startTime;
      else
        return -1;
    }
    else if (it->isUsed)
      startTime = it->getAdjustedTime();
  }
  return -1;
}


wstring oCard::getRogainingSplit(int ix, int startTime) const
{
  oPunchList::const_iterator it;
  for (it = punches.begin(); it != punches.end(); ++it) {
    int t = it->getAdjustedTime();
    if (0 == ix--) {
      if (t > 0 && t > startTime)
        return formatTime(t - startTime);
    }
    if (it->isUsed)
      startTime = t;
  }
 return makeDash(L"-");
}

void oCard::remove()
{
  if (oe)
    oe->removeCard(Id);
}

bool oCard::canRemove() const
{
  return getOwner() == 0;
}

pair<int, int> oCard::getTimeRange() const {
  pair<int, int> t(24*3600, 0);
  for(oPunchList::const_iterator it = punches.begin(); it != punches.end(); ++it) {
    if (it->Time > 0) {
      t.first = min(t.first, it->Time);
      t.second = max(t.second, it->Time);
    }
  }
  return t;
}

void oCard::getPunches(vector<pPunch> &punchesOut) const {
  punchesOut.clear();
  punchesOut.reserve(punches.size());
  for(oPunchList::const_iterator it = punches.begin(); it != punches.end(); ++it) {
    punchesOut.push_back(pPunch(&*it));
  }
}

bool oCard::comparePunchTime(oPunch *p1, oPunch *p2) {
  return p1->Time < p2->Time;
}

void oCard::setupFromRadioPunches(oRunner &r) {
  oe->synchronizeList(oLPunchId, true, true);
  vector<pFreePunch> p;
  oe->getPunchesForRunner(r.getId(), p);

  sort(p.begin(), p.end(), comparePunchTime);

  for (size_t k = 0; k < p.size(); k++)
    addPunch(p[k]->Type, p[k]->Time, 0);

  cardNo = r.getCardNo();
  readId = ConstructedFromPunches; //Indicates
}

void oCard::changedObject() {
  if (tOwner)
    tOwner->markClassChanged(-1);
}

int oCard::getNumControlPunches(int startPunchType, int finishPunchType) const {
  int count = 0;
  for(oPunchList::const_iterator it = punches.begin(); it != punches.end(); ++it) {
    if (it->isFinish(finishPunchType) || it->isCheck() || it->isStart(startPunchType)) {
      continue;
    }
    count++;
  }
  return count;
}

void oCard::adaptTimes(int startTime) {
  int st = -1;
  oPunchList::iterator it = punches.begin();
  while (it != punches.end()) {
    if (it->Time > 0) {
      st = it->Time;
      break;
    }
    ++it;
  }
  
  if (st == -1)
    return;

  const int h24 = 24 * 3600;
  int offset = st / h24;
  if (offset > 0) {
    for (it = punches.begin(); it != punches.end(); ++it) {
      if (it->Time > 0 && it->Time < offset * h24)
        return; // Inconsistent, do nothing
    }

     
    for (it = punches.begin(); it != punches.end(); ++it) {
      if (it->Time > 0)
        it->Time -= offset * h24;
    }
    updateChanged();
  }

  if (startTime >= h24) {
    offset = startTime / h24;
    for (it = punches.begin(); it != punches.end(); ++it) {
      if (it->Time > 0)
        it->Time += offset * h24;
    }
    updateChanged();
  }
}