/* bzflag
 * Copyright (c) 1993-2017 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#ifndef __VOTINGARBITER_H__
#define __VOTINGARBITER_H__

#include "common.h"
#include <iostream>
#include <deque>
#include <string>

#include "bzfsAPI.h"
#include "VotingBooth.h"
#include "TimeKeeper.h"


/** VotingArbiter is a means to manage and enforce a poll.  The poll will
 * maintain a voting booth when a poll is started.  As the poll is
 * updated, it verifies if a vote was succesfully passed.
 *
 * voteTime is how long a poll will allow players to vote
 * vetoTime is how long after a poll is successful that a veto is possible
 * votesRequired is a minimum number of votes required
 * votePercentage is a minimum percentage (0-100) required to succeed
 * voteRepeatTime is how frequently a player may request a poll
 */
class VotingArbiter
{
  // book-keeping to minimize abuse
  typedef struct poller {
    std::string name;
    TimeKeeper lastRequest;
  } poller_t;


private:
  VotingBooth *_votingBooth;

  TimeKeeper _startTime;

  // this is the number of players capable of participating in the poll.
  // it is a dynamic count of how many players are allowed to vote.
  unsigned short int _maxVotes;

  // server-configurable poll voting behavior variables
  unsigned short int _voteTime;
  unsigned short int _vetoTime;
  unsigned short int _votesRequired;
  float _votePercentage;
  unsigned short int _voteRepeatTime;

  /* queue of people who have recently successfully initiated a poll.
   * people on the poller list cannot initiate a poll.
   */
  std::deque<poller_t> _pollers;

  /* who and what are being voted on, and who asked for the poll */
  std::string _pollee;
  std::string _polleeIP;
  std::string _action;

  // The player ID of the person starting the poll
  int _pollOrganizer;

  /* names of players who are allowed to vote */
  std::deque<std::string> _suffraged;

protected:

  /** remove people in the pollers vector that are able to poll again
   */
  void updatePollers(void);

  /** see if a person is in the list of people waiting for a repoll
   * timeout.
   */
  bool isPollerWaiting(const std::string &name) const;


public:

  /* default constructor */
  VotingArbiter(unsigned short int voteTime, unsigned short int vetoTime,
		unsigned short int votesRequired, float votePercentage,
		unsigned short int voteRepeatTime);
  /* default copy constructor */
  VotingArbiter(const VotingArbiter& arbiter);
  ~VotingArbiter(void);

  /** is there a poll that this arbiter knows about?
   */
  inline bool knowsPoll(void) const;

  /** reset/forget the poll if one is known
   */
  bool forgetPoll(void);

  /** is there a poll that is accepting votes?
   */
  inline bool isPollOpen(void) const;

  /** is there a poll that is not accepting votes?
   */
  inline bool isPollClosed(void) const;

  /** is there a poll that has completed (poll time + veto time)
   */
  inline bool isPollExpired(void) const;

  /** return a string representing the action that is presently set
   */
  inline std::string getPollAction(void) const;

  /** return a string representing the target (player or setting) being voted upon
   */
  inline std::string getPollTarget(void) const;

  /** return a string representing the IP of the player being voted
   * upon, this is mostly useful for placing a ban.
   */
  inline std::string getPollTargetIP(void) const;

  /** return the ID of the player who requested the poll
   */
  inline int getPollOrganizer(void) const;


  /** attempt to activate/open a poll
   */
  bool poll(const std::string &victim, int playerRequestingID, std::string action, std::string playerIP="");

  /** convenience method to attempt to start a kick poll
   */
  bool pollToKick(const std::string &victim, int playerRequestingID, const std::string &playerIP);

  /** convenience method to attempt to start a kill poll
   */
  bool pollToKill(const std::string &victim, int playerRequestingID, const std::string &playerIP);

  /** convenience method to attempt to start a ban poll
   */
  bool pollToBan(const std::string &victim, int playerRequestingID, const std::string &playerIP);
  /** convenience method to attempt to start a set poll
   */
  bool pollToSet(const std::string &setting, int playerRequestingID);

  /** convenience method to attempt to reset flags that, at the time, are unused
   */
  bool pollToResetFlags(const int playerRequestingID);

  /** halt/close the poll if it is open
   */
  bool closePoll(void);


  /** set the number of available voters
   */
  bool setAvailableVoters(unsigned short int count);

  /** grant a player the right to vote
   */
  bool grantSuffrage(const std::string &player);

  /** returns whether truthfully whether a certain player is permitted
   * to vote; a player should check their right to vote before voting.
   */
  bool hasSuffrage(const std::string &player) const;

  /**
   * returns whether or not a user has voted
   */
  bool hasVoted(const std::string &player) const;

  /** apply a yes vote; returns true if the vote could be made
   */
  bool voteYes(const std::string &player);
  /** apply a no vote; returns true if the vote could be made
   */
  bool voteNo(const std::string &player);

  /** returns the number of yes votes received
   */
  unsigned long int getYesCount(void) const;

  /** returns the number of no votes received
   */
  unsigned long int getNoCount(void) const;

  /** returns the number of abstentions
   */
  unsigned long int getAbstentionCount(void) const;

  /** returns the vote duration length of the poll
   */
  unsigned short int getVoteTime(void) const;

  /** returns the veto duration length of the poll
   */
  unsigned short int getVetoTime(void) const;

  /** returns the time the poll was started
   */
  TimeKeeper getStartTime(void) const;

  /** returns truthfully if the poll has reached a passable tally.
   * i.e. enough positive votes have been received that the vote is
   * successful.
   */
  bool isPollSuccessful(void) const;

  /** returns truthfully if the poll is bound to fail given the
   * known votes and required vote percentage
   */
  bool isPollSureToFail(void) const;

  /** returns how much time (in seconds) is remaining on a poll, if one is
   * active
   */
  unsigned short int timeRemaining(void) const;

  /** allow a player vote to be retracted (perhaps if a player leaves
   */
  bool retractVote(const std::string &player);

};


inline VotingArbiter::VotingArbiter(unsigned short int voteTime,
				    unsigned short int vetoTime,
				    unsigned short int votesRequired,
				    float votePercentage,
				    unsigned short int voteRepeatTime)
  : _votingBooth(NULL),
    _maxVotes(votesRequired+1),
    _voteTime(voteTime),
    _vetoTime(vetoTime),
    _votesRequired(votesRequired),
    _votePercentage(votePercentage),
    _voteRepeatTime(voteRepeatTime)
{
  _startTime = TimeKeeper::getNullTime();
  _pollee = "nobody";
  _polleeIP = "";
  _action = "";
  _pollOrganizer = BZ_SERVER;
  return;
}

inline VotingArbiter::VotingArbiter(const VotingArbiter& arbiter)
  : _votingBooth(arbiter._votingBooth),
    _startTime(arbiter._startTime),
    _maxVotes(arbiter._maxVotes),
    _voteTime(arbiter._voteTime),
    _vetoTime(arbiter._vetoTime),
    _votesRequired(arbiter._votesRequired),
    _votePercentage(arbiter._votePercentage),
    _voteRepeatTime(arbiter._voteRepeatTime),
    _pollers(arbiter._pollers),
    _pollee(arbiter._pollee),
    _polleeIP(arbiter._polleeIP),
    _action(arbiter._action),
    _pollOrganizer(arbiter._pollOrganizer),
    _suffraged(arbiter._suffraged)
{
  return;
}

inline VotingArbiter::~VotingArbiter(void)
{
  if (_votingBooth != NULL) {
    delete _votingBooth;
    _votingBooth = NULL;
  }
  return;
}


inline bool VotingArbiter::knowsPoll(void) const
{
  return (_votingBooth != NULL);
}

inline bool VotingArbiter::isPollClosed(void) const
{
  if (!this->knowsPoll()) {
    return false;
  }
  // check timer
  if ((TimeKeeper::getCurrent() - _startTime) >= _voteTime) {
    return true;
  }
  return false;
}

inline bool VotingArbiter::isPollOpen(void) const
{
  if (!this->knowsPoll()) {
    return false;
  }
  if ((TimeKeeper::getCurrent() - _startTime) < _voteTime) {
    return true;
  }
  return false;
}

inline bool VotingArbiter::isPollExpired(void) const
{
  // if the poll is not at least closed, then it's not expired
  if (!this->knowsPoll()) {
    return false;
  }
  // check timer
  if ((TimeKeeper::getCurrent() - _startTime) > _voteTime + _vetoTime) {
    return true;
  }
  return false;
}

inline std::string VotingArbiter::getPollAction(void) const
{
  if (_action.size() <= 0) {
    return "do something unknown to";
  }
  return _action;
}

inline std::string VotingArbiter::getPollTarget(void) const
{
  return _pollee.size() == 0 ? "nobody" : _pollee;
}

inline std::string VotingArbiter::getPollTargetIP(void) const
{
  return _polleeIP.size() == 0 ? "" : _polleeIP;
}

inline int VotingArbiter::getPollOrganizer() const
{
  return _pollOrganizer;
}

inline unsigned short int VotingArbiter::getVoteTime(void) const
{
  return _voteTime;
}

inline unsigned short int VotingArbiter::getVetoTime(void) const
{
  return _vetoTime;
}

inline TimeKeeper VotingArbiter::getStartTime(void) const
{
  if (!this->knowsPoll()) {
    return TimeKeeper::getNullTime();
  }
  return _startTime;
}

struct PollType {
  std::string pollParameters;
  bz_CustomPollTypeHandler* pollHandler;

  PollType() :
    pollParameters(""), pollHandler(0)
  {}
};

extern std::map<std::string, PollType> customPollTypes;

void registerCustomPollType ( const char* object, const char* parameters, bz_CustomPollTypeHandler *handler );
void removeCustomPollType ( const char* object );

#else
class VotingArbiter;
#endif

// Local Variables: ***
// mode: C++ ***
// tab-width: 8 ***
// c-basic-offset: 2 ***
// indent-tabs-mode: t ***
// End: ***
// ex: shiftwidth=2 tabstop=8
