contract RestrictedPLCRVoting is PLCRVoting, Whitelist {

  using SafeMath for uint256;
  using LinkedListLib for LinkedListLib.LinkedList;

  // constants used by LinkedListLib
  uint256 constant internal NULL = 0;
  uint256 constant internal HEAD = 0;
  bool constant internal PREV = false;
  bool constant internal NEXT = true;

  // TCR used to list judge stakers.
  Registry public judgeRegistry;

  QuantstampBountyData public bountyData;

  // Map that contains IDs of restricted polls that can only be voted on by the TCR
  mapping(uint256 => bool) isRestrictedPoll;

  // Map from IDs of restricted polls to the minimum number of votes needed for a bug to pass
  mapping(uint256 => uint256) minimumVotes;

  // Map from IDs of restricted polls to the amount a judge must deposit to vote
  mapping(uint256 => uint256) judgeDeposit;

  // Map from (voter x pollId) -> bool to determine whether a voter has already claimed a reward of a given poll
  mapping(address => mapping(uint256 => bool)) private voterHasClaimedReward;

  // Map from (voter x pollId) -> bool indicating whether a voter voted yes (true) or no (false) for a poll.
  // Needed due to visibility issues with Poll structs in PLCRVoting
  mapping(address => mapping(uint256 => bool)) private votedAffirmatively;

  // Recording polls voted on but not yet awarded for each voter
  mapping (address => LinkedListLib.LinkedList) private voterPolls;
  mapping (address => uint256) public voterPollsCount;

  event LogPollRestricted(uint256 pollId);

  /**
   * @dev Initializer. Can only be called once.
   * @param _registry The address of the TCR registry
   * @param _token The address of the token
   */
  function initialize(address _token, address _registry, address _bountyData) public {
    require(_token != 0 && address(token) == 0);
    require(_registry != 0 && address(judgeRegistry) == 0);
    require(_bountyData != 0 && address(bountyData) == 0);
    bountyData = QuantstampBountyData(_bountyData);
    token = EIP20Interface(_token);
    judgeRegistry = Registry(_registry);
    pollNonce = INITIAL_POLL_NONCE;
  }

  /*
   * @dev addr is of type Address which is 20 Bytes, but the TCR expects all
   * entries to be of type Bytes32. addr is first cast to Uint256 so that it
   * becomes 32 bytes long, addr is then shifted 12 bytes (96 bits) to the
   * left so the 20 important bytes are in the correct spot.
   * @param addr The address of the person who may be an judge.
   * @return true If addr is on the TCR (is an judge)
   */
  function isJudge(address addr) public view returns(bool) {
    return judgeRegistry.isWhitelisted(bytes32(uint256(addr) << 96));
  }

  /**
   * @dev Set a poll to be restricted to TCR voting
   * @param _pollId The ID of the poll
   * @param _minimumVotes The minimum number of votes needed for the vote to go through. Each voter counts as 1 vote (not weighted).
   * @param _judgeDepositAmount The deposit of a judge to vote
   */
  function restrictPoll(uint256 _pollId, uint256 _minimumVotes, uint256 _judgeDepositAmount) public onlyWhitelisted {
    isRestrictedPoll[_pollId] = true;
    minimumVotes[_pollId] = _minimumVotes;
    judgeDeposit[_pollId] = _judgeDepositAmount;
    emit LogPollRestricted(_pollId);
  }

  /**
   * @dev Set that a voter has claimed a reward for voting with the majority
   * @param _voter The address of the voter
   * @param _pollID Integer identifier associated with target poll
   */
  function setVoterClaimedReward(address _voter, uint256 _pollID) public onlyWhitelisted {
    voterHasClaimedReward[_voter][_pollID] = true;
  }

  /**
   * @dev Determines whether a restricted poll has met the minimum vote requirements
   * @param _pollId The ID of the poll
   */
  function isEnoughVotes(uint256 _pollId) public view returns (bool) {
    return pollMap[_pollId].votesFor.add(pollMap[_pollId].votesAgainst) >= minimumVotes[_pollId].mul(judgeDeposit[_pollId]);
  }

  // Overridden methods from PLCRVoting. Needed for special requirements of the bounty protocol

  /**
  * @dev Overridden Initializer from PLCR Voting. Always reverts to ensure the registry is initialized in the above initialize function.
  * @param _token The address of the token
  */
  function init(address _token) public {
    require(false);
  }

  /**
   * @dev Overrides PLCRVoting to only allow TCR members to vote on restricted votes.
   *      Commits vote using hash of choice and secret salt to conceal vote until reveal.
   * @param _pollID Integer identifier associated with target poll
   * @param _secretHash Commit keccak256 hash of voter's choice and salt (tightly packed in this order)
   * @param _numTokens The number of tokens to be committed towards the target poll
   * @param _prevPollID The ID of the poll that the user has voted the maximum number of tokens in which is still less than or equal to numTokens
   */
  function commitVote(uint256 _pollID, bytes32 _secretHash, uint256 _numTokens, uint256 _prevPollID) public {
    if (isRestrictedPoll[_pollID]) {
      require(isJudge(msg.sender));
      // Note: The PLCR weights votes by numTokens, so here we use strict equality rather than '>='
      // This must be accounted for when tallying votes.
      require(_numTokens == judgeDeposit[_pollID]);
      require(bountyData.isJudgingPeriod(bountyData.getBugBountyId(bountyData.getBugIdFromPollId(_pollID))));
    }
    super.commitVote(_pollID, _secretHash, _numTokens, _prevPollID);
  }

  /**
   * @dev Overrides PLCRVoting to track which polls are associated with a voter.
   * @notice Reveals vote with choice and secret salt used in generating commitHash to attribute committed tokens
   * @param _pollID Integer identifier associated with target poll
   * @param _voteOption Vote choice used to generate commitHash for associated poll
   * @param _salt Secret number used to generate commitHash for associated poll
   */
  function revealVote(uint256 _pollID, uint256 _voteOption, uint256 _salt) public {
    address voter = msg.sender;
    // record the vote
    if (_voteOption == 1) {
      votedAffirmatively[voter][_pollID] = true;
    }
    // do not allow multiple votes for the same poll
    require(!voterPolls[voter].nodeExists(_pollID));
    bool wasPassing = isPassing(_pollID);
    bool wasEnoughVotes = isEnoughVotes(_pollID);
    voterPolls[voter].push(_pollID, PREV);
    voterPollsCount[voter] = voterPollsCount[voter].add(1);
    super.revealVote(_pollID, _voteOption, _salt);
    bool voteIsPassing = isPassing(_pollID);
    bountyData.updateNumApprovedBugs(_pollID, wasPassing, voteIsPassing, wasEnoughVotes);
  }

  function removePollFromVoter (address _voter, uint256 _pollID) public onlyWhitelisted returns (bool) {
    if (voterPolls[_voter].remove(_pollID) != 0) {
      voterPollsCount[_voter] = voterPollsCount[_voter] - 1;
      return true;
    }
    return false;
  }

  /**
   * @dev Determines if proposal has more affirmative votes
   *      Check if votesFor out of totalVotes exceeds votesQuorum (does not require pollEnded)
   * @param _pollID Integer identifier associated with target poll
   */
  function isPassing(uint _pollID) public view returns (bool) {
    Poll memory poll = pollMap[_pollID];
    return (100 * poll.votesFor) > (poll.voteQuorum * (poll.votesFor + poll.votesAgainst));
  }

  /**
   * @dev Gets the total winning votes for reward distribution purposes.
   *      Returns 0 if there were not enough votes.
   * @param _pollID Integer identifier associated with target poll
   * @return Total number of votes committed to the winning option for specified poll
   */
  function getTotalNumberOfTokensForWinningOption(uint _pollID) constant public returns (uint256) {
    if (isRestrictedPoll[_pollID] && !isEnoughVotes(_pollID)) {
      return 0;
    }
    return super.getTotalNumberOfTokensForWinningOption(_pollID);
  }

  /**
   * @dev Gets the number of tokens allocated toward the winning vote for a particular voter.
   *      Zero if there were not enough votes for a restricted poll.
   * @param _voter Address of voter who voted in the majority bloc
   * @param _pollID Integer identifier associated with target poll
   * @return correctVotes Number of tokens voted for winning option
   */
  function getNumPassingTokens(address _voter, uint _pollID) public constant returns (uint256) {
    if (isRestrictedPoll[_pollID] && !isEnoughVotes(_pollID)) {
      return 0;
    }
    return super.getNumPassingTokens(_voter, _pollID);
  }

  /**
   * @dev Determines if proposal has passed
   *      Check if votesFor out of totalVotes exceeds votesQuorum (requires pollEnded)
   * @param _pollID Integer identifier associated with target poll
   */
  function isPassed(uint _pollID) constant public returns (bool) {
    if (isRestrictedPoll[_pollID] && !isEnoughVotes(_pollID)) {
      return false;
    }
    return super.isPassed(_pollID);
  }

  /**
   * @dev Determines if a voter has already claimed a reward for voting with the majority
   * @param _voter The address of the voter
   * @param _pollID Integer identifier associated with target poll
   */
  function hasVoterClaimedReward(address _voter, uint256 _pollID) public view returns (bool) {
    return voterHasClaimedReward[_voter][_pollID];
  }

  /**
   * @dev Determines if a voter voted yes or no for a poll
   * @param _voter The address of the voter
   * @param _pollID Integer identifier associated with target poll
   */
  function hasVotedAffirmatively(address _voter, uint256 _pollID) public view returns (bool) {
    return votedAffirmatively[_voter][_pollID];
  }

  /**
   * @dev Returns the number of unclaimed polls associated with the voter.
   * @param _voter The address of the voter
   */
  function getVoterPollsCount (address _voter) public view returns (uint256) {
    return voterPollsCount[_voter];
  }

  function getListHeadConstant () public pure returns(uint256 head) {
    return HEAD;
  }

  /**
   * @dev Given a pollID, it retrieves the next pollID voted on but unclaimed by the voter.
   * @param _voter The address of the voter
   * @param _prevPollID The id of the previous unclaimed poll. Passing 0, it returns the first poll.
   */
  function getNextPollFromVoter(address _voter, uint256 _prevPollID) public view returns (bool, uint256) {
    if (!voterPolls[_voter].listExists()) {
      return (false, 0);
    }
    uint256 pollID;
    bool exists;
    (exists, pollID) = voterPolls[_voter].getAdjacent(_prevPollID, NEXT);
    if (!exists || pollID == 0) {
      return (false, 0);
    }
    return (true, pollID);
  }
}
/**
 Extends PLCR Voting to have restricted polls that can only be voted on by the TCR.
 */
function initialize(address _token, address _registry, address _bountyData) public {
  require(_token != 0 && address(token) == 0);
  require(_registry != 0 && address(judgeRegistry) == 0);
  require(_bountyData != 0 && address(bountyData) == 0);
  bountyData = QuantstampBountyData(_bountyData);
  token = EIP20Interface(_token);
  judgeRegistry = Registry(_registry);
  pollNonce = INITIAL_POLL_NONCE;
}

/**
 * @dev Initializer. Can only be called once.
 * @param _registry The address of the TCR registry
 * @param _token The address of the token
 */