contract AuctionableWorldCupPlayerToken is WorldCupPlayerToken {

  Auction[] public auctions;
  struct Auction {
    address highestBidder;
    uint256 highestBidAmount;
    uint256 endsAtBlock;
    uint256 playerId;
    bool finalized;
    string tokenURI;
  }
  uint256 constant MIN_BID_INCREMENT = 0.01 ether;
  uint256 constant AUCTION_BLOCK_LENGTH = 257;
  uint256 public totalUnclaimedBidsAmount = 0;
  mapping (uint256 => mapping(address => uint256)) public unclaimedBidsByAuctionIndexByBidder;

  event AuctionCreated(uint256 playerId);
  event AuctionFinalized(uint256 auctionIndex, address highestBidder);
  event BidIncremented(uint256 auctionIndex, address bidder);
  event BidReturned(uint256 auctionIndex, address bidder);

  /** @dev Returns the total number of Auctions.
    * @return total number of Auctions.
    */
  function totalAuctionsCount() public view returns (uint256) {
    return auctions.length;
  }

  /** @dev Starts an Auction by setting endsAtBlock past current block.
    * @param auctionIndex Index of Auction in auctions array.
    */
  function _startAuction(uint256 auctionIndex) internal {
    auctions[auctionIndex].endsAtBlock = block.number + AUCTION_BLOCK_LENGTH;
  }

  /** @dev Creates a new Token Auction.
    * @param playerId playerId to be auctioned.
    * @param tokenURI IPFS hash of player metadata.
    */
  function _createAuction(uint256 playerId, string tokenURI) internal {
    playerCount[playerId] += 1;
    Auction memory auction = Auction(address(0), 0, 0, playerId, false, tokenURI);
    auctions.push(auction);
    emit AuctionCreated(playerId);
  }

  /** @dev Creates a new Legendary Token Auction.
    * @param playerId playerId to be auctioned.
    * @param tokenURI IPFS hash of player metadata.
    */
  function createLegendaryAuction(uint256 playerId, string tokenURI)
    public
    onlyOwner
    enforcePlayerScarcity(playerId, DISTINCT_LEGENDARY_LIMIT)
    validatePlayerIdRange(playerId, 0, LEGENDARY_MAX_ID)
  {
    _createAuction(playerId, tokenURI);
  }

  /** @dev Creates a new Rare Token Auction.
    * @param playerId playerId to be auctioned.
    * @param tokenURI IPFS hash of player metadata.
    */
  function createRareAuction(uint256 playerId, string tokenURI)
    public
    onlyOwner
    enforcePlayerScarcity(playerId, DISTINCT_RARE_LIMIT)
    validatePlayerIdRange(playerId, LEGENDARY_MAX_ID, RARE_MAX_ID)
  {
    _createAuction(playerId, tokenURI);
  }

  /** @dev Creates a new Common Token Auction.
    * @param playerId playerId to be auctioned.
    * @param tokenURI IPFS hash of player metadata.
    */
  function createCommonAuction(uint256 playerId, string tokenURI)
    public
    onlyOwner
    enforcePlayerScarcity(playerId, DISTINCT_COMMON_LIMIT)
    validatePlayerIdRange(playerId, RARE_MAX_ID, COMMON_MAX_ID)
  {
    _createAuction(playerId, tokenURI);
  }

  /** @dev Increments a sender's bid for a given Auction.
    * @param auctionIndex Index in auctions of Auction to increment bid on.
    */
  function incrementBid(uint256 auctionIndex) public payable {

    // Require auction to exist:
    require(auctionIndex + 1 <= auctions.length, "Auction does not exist.");

    // Require auction to not be ended:
    uint256 auctionEndsAtBlock = auctions[auctionIndex].endsAtBlock;
    require(auctionEndsAtBlock == 0 || block.number < auctionEndsAtBlock, "Auction has ended.");

    // Calculate new total bid from sender:
    uint256 newTotalBid = unclaimedBidsByAuctionIndexByBidder[auctionIndex][msg.sender] + msg.value;

    // Require new highest bid to be increased by minimum bid increment:
    require(newTotalBid >= auctions[auctionIndex].highestBidAmount + MIN_BID_INCREMENT, "Must increment bid by MIN_BID_INCREMENT.");

    // Start auction if this is the first bid:
    if (auctions[auctionIndex].endsAtBlock == 0) {
      _startAuction(auctionIndex);
    }

    // Set bidder to highest bidder:
    auctions[auctionIndex].highestBidder = msg.sender;

    // Set new highest bid amount:
    auctions[auctionIndex].highestBidAmount = newTotalBid;

    // Update how much ether the contract is holding for bidder:
    unclaimedBidsByAuctionIndexByBidder[auctionIndex][msg.sender] += msg.value;

    // Update total amount of ether the contract is holding for bidders:
    totalUnclaimedBidsAmount += msg.value;

    emit BidIncremented(auctionIndex, msg.sender);
  }

  /** @dev Finalizes an Auction, transfering token to the highest bidder.
    * @param auctionIndex Index in auctions of Auction to finalize.
    */
  function finalizeAuction(uint256 auctionIndex) public {

    // Require auction to exist:
    require(auctionIndex + 1 <= auctions.length, "Auction does not exist.");

    // Require auction to not already be finalized:
    require(auctions[auctionIndex].finalized == false, "Auction has already been finalized.");

    // Require auction to be ended:
    require(block.number > auctions[auctionIndex].endsAtBlock, "Auction has not ended yet.");

    // Finalize auction:
    auctions[auctionIndex].finalized = true;

    Auction memory auction = auctions[auctionIndex];

    // Decrement winning bid from total unclaimed bids amount (that can be withdrawn):
    totalUnclaimedBidsAmount = totalUnclaimedBidsAmount - auction.highestBidAmount;

    // Remove unclaimedBids from winning bidder:
    unclaimedBidsByAuctionIndexByBidder[auctionIndex][auction.highestBidder] = 0;

    // Mint token:
    _mintToken(auction.playerId, auction.tokenURI, auction.highestBidder);

    emit AuctionFinalized(auctionIndex, auction.highestBidder);
  }

  /** @dev Withdraws broker funds.
    */
  function withdraw() public onlyOwner {
    uint256 amount = address(this).balance - totalUnclaimedBidsAmount;
    owner.transfer(amount);
  }

  /** @dev Returns bids for a given Auction to bidder.
    * @param auctionIndex Index in auctions of Auction to return bids for.
    * @param bidder Address of bidder.
    */
  function returnBids(uint256 auctionIndex, address bidder) public {

    // Require auction to exist:
    require(auctionIndex + 1 <= auctions.length, "Auction does not exist.");

    // Require auction to be ended:
    require(block.number > auctions[auctionIndex].endsAtBlock, "Auction has not ended yet.");

    // Require bidder not the winning address:
    require(bidder != auctions[auctionIndex].highestBidder, "Bidder who won auction cannot return bids.");

    // Cache refund amount:
    uint256 refund = unclaimedBidsByAuctionIndexByBidder[auctionIndex][bidder];

    // Reset unclaimed bids amount:
    unclaimedBidsByAuctionIndexByBidder[auctionIndex][bidder] = 0;

    // Refund unclaimed bids to bidder:
    bidder.transfer(refund);

    emit BidReturned(auctionIndex, bidder);
  }

}
function createCommonAuction(uint256 playerId, string tokenURI)
  public
  onlyOwner
  enforcePlayerScarcity(playerId, DISTINCT_COMMON_LIMIT)
  validatePlayerIdRange(playerId, RARE_MAX_ID, COMMON_MAX_ID)
{
  _createAuction(playerId, tokenURI);
}

/** @dev Creates a new Common Token Auction.
 * @param playerId playerId to be auctioned.
 * @param tokenURI IPFS hash of player metadata.
 */