contract QuantstampBountyData is 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;


  uint256 constant internal NUMBER_OF_PHASES = 3;

  struct Bounty {
    address submitter;
    string contractAddress;
    uint256 size; // R1
    uint256 minVotes; // R3
    uint256 duration; // R2. Number of seconds
    uint256 judgeDeposit; // R5
    uint256 hunterDeposit; // R6
    uint256 initiationTimestamp; // Time in seconds that the bounty is created
    bool remainingFeesWithdrawn; // true if the remaining fees have been withdrawn by the submitter
    uint256 numApprovedBugs;
  }

  // holds information about a revealed bug
  struct Bug {
    address hunter; // address that submitted the hash
    uint256 bountyId; // the ID of the associated bounty
    string bugDescription; // the description of the bug
    uint256 numTokens; // the number of tokens staked on the commit
    uint256 pollId; // the poll that decided on the validity of the bug
  }

  // holds information relevant to a bug commit
  struct BugCommit {
    address hunter;  // address that submitted the hash
    uint256 bountyId;  // the ID of the associated bounty
    bytes32 bugDescriptionHash;  // keccak256 hash of the bug
    uint256 commitTimestamp;  // Time in seconds that the bug commit occurred
    uint256 revealStartTimestamp;  // Time in seconds when the the reveal phase starts
    uint256 revealEndTimestamp;  // Time in seconds when the the reveal phase ends
    uint256 numTokens;  // the number of tokens staked on the commit
  }

  mapping (uint256 => Bounty) public bounties;

  // maps pollIds back to bugIds
  mapping (uint256 => uint256) public pollIdToBugId;

  // For generating bountyIDs starting from 1
  uint256 private bountyCounter;

  // For generating bugIDs starting from 1
  uint256 private bugCounter;

  // token used to pay for participants in a bounty. This contract assumes that the owner of the contract
  // trusts token's code and that transfer function (such as transferFrom, transfer) do the right thing
  StandardToken public token;

  // The partial-locking commit-reveal voting interface used by the TCR to determine the validity of bugs
  RestrictedPLCRVoting public voting;

  // The underlying contract to hold PLCR voting parameters
  Parameterizer public parameterizer;

  // Recording reported bugs not yet awarded for each hunter
  mapping (address => LinkedListLib.LinkedList) private hunterReportedBugs;
  mapping (address => uint256) public hunterReportedBugsCount;

  /**
   * @dev The constructor creates a QuantstampBountyData contract.
   * @param tokenAddress The address of a StandardToken that will be used to pay auditor nodes.
   */
  constructor (address tokenAddress, address votingAddress, address parameterizerAddress) public {
    require(tokenAddress != address(0));
    require(votingAddress != address(0));
    require(parameterizerAddress != address(0));
    token = StandardToken(tokenAddress);
    voting = RestrictedPLCRVoting(votingAddress);
    parameterizer = Parameterizer(parameterizerAddress);
  }

  // maps bountyIDs to list of corresponding bugIDs
  // each list contains all revealed bugs for a given bounty, ordered by time
  // NOTE: this cannot be part of the Bounty struct due to solidity limitations
  mapping(uint256 => LinkedListLib.LinkedList) private bugLists;

  // maps bugIDs to BugCommits
  mapping(uint256 => BugCommit) public bugCommitMap;

  // maps bugIDs to revealed bugs; uses the same ID as the bug commit
  mapping(uint256 => Bug) public bugs;

  function addBugCommitment(address hunter,
                            uint256 bountyId,
                            bytes32 bugDescriptionHash,
                            uint256 hunterDeposit) public onlyWhitelisted returns (uint256) {
    bugCounter = bugCounter.add(1);
    bugCommitMap[bugCounter] = BugCommit({
      hunter: hunter,
      bountyId: bountyId,
      bugDescriptionHash: bugDescriptionHash,
      commitTimestamp: block.timestamp,
      revealStartTimestamp: getBountyRevealPhaseStartTimestamp(bountyId),
      revealEndTimestamp: getBountyRevealPhaseEndTimestamp(bountyId),
      numTokens: hunterDeposit
    });
    return bugCounter;
  }

  function addBug(uint256 bugId, string bugDescription, uint256 pollId) public onlyWhitelisted returns (bool) {
    // create a Bug
    bugs[bugId] = Bug({
      hunter: bugCommitMap[bugId].hunter,
      bountyId: bugCommitMap[bugId].bountyId,
      bugDescription: bugDescription,
      numTokens: bugCommitMap[bugId].numTokens,
      pollId: pollId
    });
    // add pointer to it in the corresponding bounty
    bugLists[bugCommitMap[bugId].bountyId].push(bugId, PREV);
    pollIdToBugId[pollId] = bugId;
    return true;
  }

  function addBounty (address submitter,
                      string contractAddress,
                      uint256 size,
                      uint256 minVotes,
                      uint256 duration,
                      uint256 judgeDeposit,
                      uint256 hunterDeposit) public onlyWhitelisted returns(uint256) {
    bounties[++bountyCounter] = Bounty(submitter,
                                        contractAddress,
                                        size,
                                        minVotes,
                                        duration,
                                        judgeDeposit,
                                        hunterDeposit,
                                        block.timestamp,
                                        false,
                                        0);
    return bountyCounter;
  }

  function removeBugCommitment(uint256 bugId) public onlyWhitelisted returns (bool) {
    delete bugCommitMap[bugId];
    return true;
  }

  /**
   * @dev Sets a new value for the number of approved bugs, if appropriate
   * @param pollId The ID of the poll being that a vote was received for
   * @param wasPassing true if and only if more affirmative votes prior to this vote
   * @param isPassing true if and only if more affirmative votes after this vote
   * @param wasEnoughVotes true if and only if quorum was reached prior to this vote
   */
  function updateNumApprovedBugs(uint256 pollId, bool wasPassing, bool isPassing, bool wasEnoughVotes) public {
    require(msg.sender == address(voting));
    uint256 bountyId = getBugBountyId(getBugIdFromPollId(pollId));

    if (wasEnoughVotes) {
      if (!wasPassing && isPassing) {
        bounties[bountyId].numApprovedBugs = bounties[bountyId].numApprovedBugs.add(1);
      } else if (wasPassing && !isPassing) {
        bounties[bountyId].numApprovedBugs = bounties[bountyId].numApprovedBugs.sub(1);
      }
    } else if (voting.isEnoughVotes(pollId) && isPassing) {
      bounties[bountyId].numApprovedBugs = bounties[bountyId].numApprovedBugs.add(1);
    }
  }

  /**
   * @dev Reports the number of approved bugs of a bounty
   * @param bountyId The ID of the bounty.
   */
  function getNumApprovedBugs(uint256 bountyId) public view returns (uint256) {
    return bounties[bountyId].numApprovedBugs;
  }

  /**
   * @dev Sets remainingFeesWithdrawn to true after the submitter withdraws.
   * @param bountyId The ID of the bounty.
   */
  function setBountyRemainingFeesWithdrawn (uint256 bountyId) public onlyWhitelisted {
    bounties[bountyId].remainingFeesWithdrawn = true;
  }

  function addBugToHunter (address hunter, uint256 bugId) public onlyWhitelisted {
    hunterReportedBugs[hunter].push(bugId, PREV);
    hunterReportedBugsCount[hunter] = hunterReportedBugsCount[hunter].add(1);
  }

  function removeBugFromHunter (address hunter, uint256 bugId) public onlyWhitelisted returns (bool) {
    if (hunterReportedBugs[hunter].remove(bugId) != 0) {
      hunterReportedBugsCount[hunter] = hunterReportedBugsCount[hunter].sub(1);
      bugs[bugId].hunter = 0x0;
      return true;
    }
    return false;
  }

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

  function getBountySubmitter (uint256 bountyId) public view returns(address) {
    return bounties[bountyId].submitter;
  }

  function getBountyContractAddress (uint256 bountyId) public view returns(string) {
    return bounties[bountyId].contractAddress;
  }

  function getBountySize (uint256 bountyId) public view returns(uint256) {
    return bounties[bountyId].size;
  }

  function getBountyMinVotes (uint256 bountyId) public view returns(uint256) {
    return bounties[bountyId].minVotes;
  }

  function getBountyDuration (uint256 bountyId) public view returns(uint256) {
    return bounties[bountyId].duration;
  }

  function getBountyJudgeDeposit (uint256 bountyId) public view returns(uint256) {
    return bounties[bountyId].judgeDeposit;
  }

  function getBountyHunterDeposit (uint256 bountyId) public view returns(uint256) {
    return bounties[bountyId].hunterDeposit;
  }

  function getBountyInitiationTimestamp (uint256 bountyId) public view returns(uint256) {
    return bounties[bountyId].initiationTimestamp;
  }

  function getBountyCommitPhaseEndTimestamp (uint256 bountyId) public view returns(uint256) {
    return bounties[bountyId].initiationTimestamp.add(getBountyDuration(bountyId).div(NUMBER_OF_PHASES));
  }

  function getBountyRevealPhaseStartTimestamp (uint256 bountyId) public view returns(uint256) {
    return getBountyCommitPhaseEndTimestamp(bountyId).add(1);
  }

  function getBountyRevealPhaseEndTimestamp (uint256 bountyId) public view returns(uint256) {
    return getBountyCommitPhaseEndTimestamp(bountyId).add(getBountyDuration(bountyId).div(NUMBER_OF_PHASES));
  }

  function getBountyJudgePhaseStartTimestamp (uint256 bountyId) public view returns(uint256) {
    return getBountyRevealPhaseEndTimestamp(bountyId).add(1);
  }

  function getBountyJudgePhaseEndTimestamp (uint256 bountyId) public view returns(uint256) {
    return bounties[bountyId].initiationTimestamp.add(getBountyDuration(bountyId));
  }

  function getBountyJudgeCommitPhaseEndTimestamp (uint256 bountyId) public view returns(uint256) {
    uint256 judgePhaseDuration = getBountyDuration(bountyId).div(NUMBER_OF_PHASES);
    return getBountyJudgePhaseStartTimestamp(bountyId).add(judgePhaseDuration.div(2));
  }

  function getBountyJudgeRevealDuration (uint256 bountyId) public view returns(uint256) {
    return getBountyJudgePhaseEndTimestamp(bountyId).sub(getBountyJudgeCommitPhaseEndTimestamp(bountyId));
  }

  function isCommitPeriod (uint256 bountyId) public view returns(bool) {
    return block.timestamp >= bounties[bountyId].initiationTimestamp && block.timestamp <= getBountyCommitPhaseEndTimestamp(bountyId);
  }

  function isRevealPeriod (uint256 bountyId) public view returns(bool) {
    return block.timestamp >= getBountyRevealPhaseStartTimestamp(bountyId) && block.timestamp <= getBountyRevealPhaseEndTimestamp(bountyId);
  }

  function isJudgingPeriod (uint256 bountyId) public view returns(bool) {
    return block.timestamp >= getBountyJudgePhaseStartTimestamp(bountyId) && block.timestamp <= getBountyJudgePhaseEndTimestamp(bountyId);
  }

  function getBountyRemainingFeesWithdrawn (uint256 bountyId) public view returns(bool) {
    return bounties[bountyId].remainingFeesWithdrawn;
  }

  function getBugCommitCommitter(uint256 bugCommitId) public view returns (address) {
    return bugCommitMap[bugCommitId].hunter;
  }

  function getBugCommitBountyId(uint256 bugCommitId) public view returns (uint256) {
    return bugCommitMap[bugCommitId].bountyId;
  }

  function getBugCommitBugDescriptionHash(uint256 bugCommitId) public view returns (bytes32) {
    return bugCommitMap[bugCommitId].bugDescriptionHash;
  }

  function getBugCommitCommitTimestamp(uint256 bugCommitId) public view returns (uint256) {
    return bugCommitMap[bugCommitId].commitTimestamp;
  }

  function getBugCommitRevealStartTimestamp(uint256 bugCommitId) public view returns (uint256) {
    return bugCommitMap[bugCommitId].revealStartTimestamp;
  }

  function getBugCommitRevealEndTimestamp(uint256 bugCommitId) public view returns (uint256) {
    return bugCommitMap[bugCommitId].revealEndTimestamp;
  }

  function getBugCommitNumTokens(uint256 bugCommitId) public view returns (uint256) {
    return bugCommitMap[bugCommitId].numTokens;
  }

  function bugRevealPeriodActive(uint256 bugCommitId) public view returns (bool) {
    return bugCommitMap[bugCommitId].revealStartTimestamp <= block.timestamp && block.timestamp <= bugCommitMap[bugCommitId].revealEndTimestamp;
  }

  function bugRevealPeriodExpired(uint256 bugCommitId) public view returns (bool) {
    return block.timestamp > bugCommitMap[bugCommitId].revealEndTimestamp;
  }

  function bugRevealDelayPeriodActive(uint256 bugCommitId) public view returns (bool) {
    return block.timestamp < bugCommitMap[bugCommitId].revealStartTimestamp;
  }

  function bountyActive(uint256 bountyId) public view returns (bool) {
    return block.timestamp <= getBountyInitiationTimestamp(bountyId).add(getBountyDuration(bountyId));
  }

  function getHunterReportedBugsCount (address hunter) public view returns (uint256) {
    return hunterReportedBugsCount[hunter];
  }

  // Bug Functions
  function getBugBountyId(uint256 bugId) public view returns (uint256) {
    return bugs[bugId].bountyId;
  }

  function getBugHunter(uint256 bugId) public view returns (address) {
    return bugs[bugId].hunter;
  }

  function getBugDescription(uint256 bugId) public view returns (string) {
    return bugs[bugId].bugDescription;
  }

  function getBugNumTokens(uint256 bugId) public view returns (uint256) {
    return bugs[bugId].numTokens;
  }

  function getBugPollId(uint256 bugId) public view returns (uint256) {
    return bugs[bugId].pollId;
  }

  function getFirstRevealedBug(uint256 bountyId) public view returns (bool, uint256, string) {
    return getNextRevealedBug(bountyId, HEAD);
  }

  function getBugIdFromPollId(uint256 pollId) public view returns (uint256) {
    return pollIdToBugId[pollId];
  }

  /*
   * @dev Gets the bug description of a revealed bug associated with a bounty
   * @param bountyId The ID of the bounty
   * @param previousBugId The ID of the previous bug in the linked list (HEAD for the first bug)
   * @return a triple containing 1) whether the bug exists; 2) its bugId (0 if non-existent); 3) the description
   */
  function getNextRevealedBug(uint256 bountyId, uint256 previousBugId) public view returns (bool, uint256, string) {
    if (!bugLists[bountyId].listExists()) {
      return (false, 0, "");
    }
    uint256 bugId;
    bool exists;
    (exists, bugId) = bugLists[bountyId].getAdjacent(previousBugId, NEXT);
    if (!exists || bugId == 0) {
      return (false, 0, "");
    }
    string memory bugDescription = bugs[bugId].bugDescription;
    return (true, bugId, bugDescription);
  }

  /**
   * @dev Given a bugId, it retrieves the next bugId reported by a hunter. Such bugs have not been cashed yet.
   * @param hunter The address of a hunter
   * @param previousBugId The id of the previous reported bug. Passing 0, it returns the first reported bug.
   */
  function getNextBugFromHunter(address hunter, uint256 previousBugId) public view returns (bool, uint256) {
    if (!hunterReportedBugs[hunter].listExists()) {
      return (false, 0);
    }
    uint256 bugId;
    bool exists;
    (exists, bugId) = hunterReportedBugs[hunter].getAdjacent(previousBugId, NEXT);
    if (!exists || bugId == 0) {
      return (false, 0);
    }
    return (true, bugId);
  }

  /**
   * @dev Determines if the judge meets the requirements to claim an award for voting in a poll
   * @param bugId Id of a bug
   * Note: moved to this contract as the Bounty contract was getting too large to deploy
   */
  function canClaimJudgeAward(address judge, uint256 bugId) public view returns (bool) {
    // NOTE: these cannot be a require statement as this check occurs in a loop that should not fail
    // the poll has concluded
    uint256 pollId = getBugPollId(bugId);
    bool pollHasConcluded = voting.pollExists(pollId) && voting.pollEnded(pollId);
    // the judge voted in the majority
    // this is needed to avoid hitting a require statement when in the minority in PLCRVoting
    bool votedWithMajority = pollHasConcluded && voting.isEnoughVotes(pollId) &&
      (voting.isPassed(pollId) && voting.hasVotedAffirmatively(judge, pollId) ||
      !voting.isPassed(pollId) && !voting.hasVotedAffirmatively(judge, pollId));
    // the judge should not have already claimed an award for this poll
    bool alreadyClaimed = voting.hasVoterClaimedReward(judge, pollId);
    // the bounty should be over
    bool bountyStillActive = bountyActive(getBugBountyId(bugId));
    return votedWithMajority && !alreadyClaimed && !bountyStillActive;
  }
}
function getNumApprovedBugs(uint256 bountyId) public view returns (uint256) {
  return bounties[bountyId].numApprovedBugs;
}

/**
 * @dev Reports the number of approved bugs of a bounty
 * @param bountyId The ID of the bounty.
 */