contract CSportsMinting is CSportsAuction {

  /// @dev MarketingTokenRedeemed event is fired when a marketing token has been redeemed
  event MarketingTokenRedeemed(uint256 hash, uint128 rwpMd5, address indexed recipient);

  /// @dev MarketingTokenCreated event is fired when a marketing token has been created
  event MarketingTokenCreated(uint256 hash, uint128 rwpMd5);

  /// @dev MarketingTokenReplaced event is fired when a marketing token has been replaced
  event MarketingTokenReplaced(uint256 oldHash, uint256 newHash, uint128 rwpMd5);

  /// @dev Sanity check that identifies this contract as having minting capability
  function isMinter() public pure returns (bool) {
      return true;
  }

  /// @dev Utility function to make it easy to keccak256 a string in python or javascript using
  /// the exact algorythm used by Solidity.
  function getKeccak256(string stringToHash) public pure returns (uint256) {
      return uint256(keccak256(abi.encodePacked(stringToHash)));
  }

  /// @dev Allows the commissioner to load up our marketingTokens mapping with up to
  /// MAX_MARKETING_TOKENS marketing tokens that can be created if one knows the words
  /// to keccak256 and match the keywordHash passed here. Use web3.utils.soliditySha3(param1 [, param2, ...])
  /// to create this hash.
  ///
  /// ONLY THE COMMISSIONER CAN CREATE MARKETING TOKENS, AND ONLY UP TO MAX_MARKETING_TOKENS OF THEM
  ///
  /// @param keywordHash - keccak256 of a known set of keyWords
  /// @param md5Token - The md5 key in the leagueRosterContract that specifies the player
  /// player token that will be minted and transfered by the redeemMarketingToken(...) method.
  function addMarketingToken(uint256 keywordHash, uint128 md5Token) public onlyCommissioner {

    require(remainingMarketingTokens > 0);
    require(marketingTokens[keywordHash] == 0);

    // Make sure the md5Token exists in the league roster
    uint128 _rosterIndex = leagueRosterContract.getRealWorldPlayerRosterIndex(md5Token);
    require(_rosterIndex != 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);

    // Map the keyword Hash to the RWP md5 and decrement the remainingMarketingTokens property
    remainingMarketingTokens--;
    marketingTokens[keywordHash] = md5Token;

    emit MarketingTokenCreated(keywordHash, md5Token);

  }

  /// @dev This method allows the commish to replace an existing marketing token that has
  /// not been used with a new one (new hash and mdt). Since we are replacing, we co not
  /// have to deal with remainingMarketingTokens in any way. This is to allow for replacing
  /// marketing tokens that have not been redeemed and aren't likely to be redeemed (breakage)
  ///
  /// ONLY THE COMMISSIONER CAN ACCESS THIS METHOD
  ///
  /// @param oldKeywordHash Hash to replace
  /// @param newKeywordHash Hash to replace with
  /// @param md5Token The md5 key in the leagueRosterContract that specifies the player
  function replaceMarketingToken(uint256 oldKeywordHash, uint256 newKeywordHash, uint128 md5Token) public onlyCommissioner {

    uint128 _md5Token = marketingTokens[oldKeywordHash];
    if (_md5Token != 0) {
      marketingTokens[oldKeywordHash] = 0;
      marketingTokens[newKeywordHash] = md5Token;
      emit MarketingTokenReplaced(oldKeywordHash, newKeywordHash, md5Token);
    }

  }

  /// @dev Returns the real world player's MD5 key from a keywords string. A 0x00 returned
  /// value means the keyword string parameter isn't mapped to a marketing token.
  /// @param keyWords Keywords to use to look up RWP MD5
  //
  /// ANYONE CAN VALIDATE A KEYWORD STRING (MAP IT TO AN MD5 IF IT HAS ONE)
  ///
  /// @param keyWords - A string that will keccak256 to an entry in the marketingTokens
  /// mapping (or not)
  function MD5FromMarketingKeywords(string keyWords) public view returns (uint128) {
    uint256 keyWordsHash = uint256(keccak256(abi.encodePacked(keyWords)));
    uint128 _md5Token = marketingTokens[keyWordsHash];
    return _md5Token;
  }

  /// @dev Allows anyone to try to redeem a marketing token by passing N words that will
  /// be SHA256'ed to match an entry in our marketingTokens mapping. If a match is found,
  /// a CryptoSports token is created that corresponds to the md5 retrieved
  /// from the marketingTokens mapping and its owner is assigned as the msg.sender.
  ///
  /// ANYONE CAN REDEEM A MARKETING token
  ///
  /// @param keyWords - A string that will keccak256 to an entry in the marketingTokens mapping
  function redeemMarketingToken(string keyWords) public {

    uint256 keyWordsHash = uint256(keccak256(abi.encodePacked(keyWords)));
    uint128 _md5Token = marketingTokens[keyWordsHash];
    if (_md5Token != 0) {

      // Only one redemption per set of keywords
      marketingTokens[keyWordsHash] = 0;

      uint128 _rosterIndex = leagueRosterContract.getRealWorldPlayerRosterIndex(_md5Token);
      if (_rosterIndex != 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) {

        // Grab the real world player record from the leagueRosterContract
        RealWorldPlayer memory _rwp;
        (_rwp.md5Token, _rwp.prevCommissionerSalePrice, _rwp.lastMintedTime, _rwp.mintedCount, _rwp.hasActiveCommissionerAuction, _rwp.mintingEnabled) =  leagueRosterContract.realWorldPlayerFromIndex(_rosterIndex);

        // Mint this player, sending it to the message sender
        _mintPlayer(uint32(_rosterIndex), _rwp.mintedCount, msg.sender);

        // Finally, update our realWorldPlayer record to reflect the fact that we just
        // minted a new one, and there is an active commish auction. The only portion of
        // the RWP record we change here is an update to the mingedCount.
        leagueRosterContract.updateRealWorldPlayer(uint32(_rosterIndex), _rwp.prevCommissionerSalePrice, uint64(now), _rwp.mintedCount + 1, _rwp.hasActiveCommissionerAuction, _rwp.mintingEnabled);

        emit MarketingTokenRedeemed(keyWordsHash, _rwp.md5Token, msg.sender);
      }

    }
  }

  /// @dev Returns an array of minimum auction starting prices for an array of players
  /// specified by their MD5s.
  /// @param _md5Tokens MD5s in the league roster for the players we are inquiring about.
  function minStartPriceForCommishAuctions(uint128[] _md5Tokens) public view onlyCommissioner returns (uint128[50]) {
    require (_md5Tokens.length <= 50);
    uint128[50] memory minPricesArray;
    for (uint32 i = 0; i < _md5Tokens.length; i++) {
        uint128 _md5Token = _md5Tokens[i];
        uint128 _rosterIndex = leagueRosterContract.getRealWorldPlayerRosterIndex(_md5Token);
        if (_rosterIndex == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) {
          // Cannot mint a non-existent real world player
          continue;
        }
        RealWorldPlayer memory _rwp;
        (_rwp.md5Token, _rwp.prevCommissionerSalePrice, _rwp.lastMintedTime, _rwp.mintedCount, _rwp.hasActiveCommissionerAuction, _rwp.mintingEnabled) =  leagueRosterContract.realWorldPlayerFromIndex(_rosterIndex);

        // Skip this if there is no player associated with the md5 specified
        if (_rwp.md5Token != _md5Token) continue;

        minPricesArray[i] = uint128(_computeNextCommissionerPrice(_rwp.prevCommissionerSalePrice));
    }
    return minPricesArray;
  }

  /// @dev Creates newly minted playerTokens and puts them up for auction. This method
  ///   can only be called by the commissioner, and checks to make sure certian minting
  ///   conditions are met (reverting if not met):
  ///     * The MD5 of the RWP specified must exist in the CSportsLeagueRoster contract
  ///     * Cannot mint a realWorldPlayer that currently has an active commissioner auction
  ///     * Cannot mint realWorldPlayer that does not have minting enabled
  ///     * Cannot mint realWorldPlayer with a start price exceeding our minimum
  ///   If any of the above conditions fails to be met, then no player tokens will be
  ///   minted.
  ///
  /// *** ONLY THE COMMISSIONER OR THE LEAGUE ROSTER CONTRACT CAN CALL THIS FUNCTION ***
  ///
  /// @param _md5Tokens - array of md5Tokens representing realWorldPlayer that we are minting.
  /// @param _startPrice - the starting price for the auction (0 will set to current minimum price)
  function mintPlayers(uint128[] _md5Tokens, uint256 _startPrice, uint256 _endPrice, uint256 _duration) public {

    require(leagueRosterContract != address(0));
    require(saleClockAuctionContract != address(0));
    require((msg.sender == commissionerAddress) || (msg.sender == address(leagueRosterContract)));

    for (uint32 i = 0; i < _md5Tokens.length; i++) {
      uint128 _md5Token = _md5Tokens[i];
      uint128 _rosterIndex = leagueRosterContract.getRealWorldPlayerRosterIndex(_md5Token);
      if (_rosterIndex == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) {
        // Cannot mint a non-existent real world player
        continue;
      }

      // We don't have to check _rosterIndex here because the getRealWorldPlayerRosterIndex(...)
      // method always returns a valid index.
      RealWorldPlayer memory _rwp;
      (_rwp.md5Token, _rwp.prevCommissionerSalePrice, _rwp.lastMintedTime, _rwp.mintedCount, _rwp.hasActiveCommissionerAuction, _rwp.mintingEnabled) =  leagueRosterContract.realWorldPlayerFromIndex(_rosterIndex);

      if (_rwp.md5Token != _md5Token) continue;
      if (!_rwp.mintingEnabled) continue;

      // Enforce the restrictions that there can ever only be a single outstanding commissioner
      // auction - no new minting if there is an active commissioner auction for this real world player
      if (_rwp.hasActiveCommissionerAuction) continue;

      // Ensure that our price is not less than a minimum
      uint256 _minStartPrice = _computeNextCommissionerPrice(_rwp.prevCommissionerSalePrice);

      // Make sure the start price exceeds our minimum acceptable
      if (_startPrice < _minStartPrice) {
          _startPrice = _minStartPrice;
      }

      // Mint the new player token
      uint32 _playerId = _mintPlayer(uint32(_rosterIndex), _rwp.mintedCount, address(this));

      // @dev Approve ownership transfer to the saleClockAuctionContract (which is required by
      //  the createAuction(...) which will escrow the playerToken)
      _approve(_playerId, saleClockAuctionContract);

      // Apply the default duration
      if (_duration == 0) {
        _duration = COMMISSIONER_AUCTION_DURATION;
      }

      // By setting our _endPrice to zero, we become immune to the USD <==> ether
      // conversion rate. No matter how high ether goes, our auction price will get
      // to a USD value that is acceptable to someone (assuming 0 is acceptable that is).
      // This also helps for players that aren't in very much demand.
      saleClockAuctionContract.createAuction(
          _playerId,
          _startPrice,
          _endPrice,
          _duration,
          address(this)
      );

      // Finally, update our realWorldPlayer record to reflect the fact that we just
      // minted a new one, and there is an active commish auction.
      leagueRosterContract.updateRealWorldPlayer(uint32(_rosterIndex), _rwp.prevCommissionerSalePrice, uint64(now), _rwp.mintedCount + 1, true, _rwp.mintingEnabled);
    }
  }

  /// @dev Reprices (and updates duration) of an array of tokens that are currently
  /// being auctioned by this contract. Since this function can only be called by
  /// the commissioner, we don't do a lot of checking of parameters and things.
  /// The SaleClockAuction's repriceAuctions method assures that the CSportsCore
  /// contract is the "seller" of the token (meaning it is a commissioner auction).
  /// @param _tokenIds Array of tokenIds corresponding to auctions being updated
  /// @param _startingPrices New starting prices for each token being repriced
  /// @param _endingPrices New ending price
  /// @param _duration New duration
  function repriceAuctions(
      uint256[] _tokenIds,
      uint256[] _startingPrices,
      uint256[] _endingPrices,
      uint256 _duration
  ) external onlyCommissioner {

      // We cannot reprice below our player minimum
      for (uint32 i = 0; i < _tokenIds.length; i++) {
          uint32 _tokenId = uint32(_tokenIds[i]);
          PlayerToken memory pt = playerTokens[_tokenId];
          RealWorldPlayer memory _rwp;
          (_rwp.md5Token, _rwp.prevCommissionerSalePrice, _rwp.lastMintedTime, _rwp.mintedCount, _rwp.hasActiveCommissionerAuction, _rwp.mintingEnabled) = leagueRosterContract.realWorldPlayerFromIndex(pt.realWorldPlayerId);
          uint256 _minStartPrice = _computeNextCommissionerPrice(_rwp.prevCommissionerSalePrice);

          // We require the price to be >= our _minStartPrice
          require(_startingPrices[i] >= _minStartPrice);
      }

      // Note we pass in this CSportsCore contract address as the seller, making sure the only auctions
      // that can be repriced by this method are commissioner auctions.
      saleClockAuctionContract.repriceAuctions(_tokenIds, _startingPrices, _endingPrices, _duration, address(this));
  }

  /// @dev Allows the commissioner to create a sale auction for a token
  ///   that is owned by the core contract. Can only be called when not paused
  ///   and only by the commissioner
  /// @param _playerTokenId - ID of the player token currently owned by the core contract
  /// @param _startingPrice - Starting price for the auction
  /// @param _endingPrice - Ending price for the auction
  /// @param _duration - Duration of the auction (in seconds)
  function createCommissionerAuction(uint32 _playerTokenId,
        uint256 _startingPrice,
        uint256 _endingPrice,
        uint256 _duration)
        public whenNotPaused onlyCommissioner {

        require(leagueRosterContract != address(0));
        require(_playerTokenId < playerTokens.length);

        // If player is already on any auction, this will throw because it will not be owned by
        // this CSportsCore contract (as all commissioner tokens are if they are not currently
        // on auction).
        // Any token owned by the CSportsCore contract by definition is a commissioner auction
        // that was canceled which makes it OK to re-list.
        require(_owns(address(this), _playerTokenId));

        // (1) Grab the real world token ID (md5)
        PlayerToken memory pt = playerTokens[_playerTokenId];

        // (2) Get the full real world player record from its roster index
        RealWorldPlayer memory _rwp;
        (_rwp.md5Token, _rwp.prevCommissionerSalePrice, _rwp.lastMintedTime, _rwp.mintedCount, _rwp.hasActiveCommissionerAuction, _rwp.mintingEnabled) = leagueRosterContract.realWorldPlayerFromIndex(pt.realWorldPlayerId);

        // Ensure that our starting price is not less than a minimum
        uint256 _minStartPrice = _computeNextCommissionerPrice(_rwp.prevCommissionerSalePrice);
        if (_startingPrice < _minStartPrice) {
            _startingPrice = _minStartPrice;
        }

        // Apply the default duration
        if (_duration == 0) {
            _duration = COMMISSIONER_AUCTION_DURATION;
        }

        // Approve the token for transfer
        _approve(_playerTokenId, saleClockAuctionContract);

        // saleClockAuctionContract.createAuction throws if inputs are invalid and clears
        // transfer after escrowing the player.
        saleClockAuctionContract.createAuction(
            _playerTokenId,
            _startingPrice,
            _endingPrice,
            _duration,
            address(this)
        );
  }

  /// @dev Computes the next commissioner auction starting price equal to
  ///  the previous real world player sale price + 25% (with a floor).
  function _computeNextCommissionerPrice(uint128 prevTwoCommissionerSalePriceAve) internal view returns (uint256) {

      uint256 nextPrice = prevTwoCommissionerSalePriceAve + (prevTwoCommissionerSalePriceAve / 4);

      // sanity check to ensure we don't overflow arithmetic (this big number is 2^128-1).
      if (nextPrice > 340282366920938463463374607431768211455) {
        nextPrice = 340282366920938463463374607431768211455;
      }

      // We never auction for less than our floor
      if (nextPrice < COMMISSIONER_AUCTION_FLOOR_PRICE) {
          nextPrice = COMMISSIONER_AUCTION_FLOOR_PRICE;
      }

      return nextPrice;
  }

}
/// @title The facet of the CSportsCore contract that manages minting new PlayerTokens
/// @author CryptoSports, Inc. (http://cryptosports.team)
/// See the CSportsCore contract documentation to understand how the various contract facets are arranged.
function redeemMarketingToken(string keyWords) public {

    uint256 keyWordsHash = uint256(keccak256(abi.encodePacked(keyWords)));
    uint128 _md5Token = marketingTokens[keyWordsHash];
    if (_md5Token != 0) {

      // Only one redemption per set of keywords
      marketingTokens[keyWordsHash] = 0;

      uint128 _rosterIndex = leagueRosterContract.getRealWorldPlayerRosterIndex(_md5Token);
      if (_rosterIndex != 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) {

        // Grab the real world player record from the leagueRosterContract
        RealWorldPlayer memory _rwp;
        (_rwp.md5Token, _rwp.prevCommissionerSalePrice, _rwp.lastMintedTime, _rwp.mintedCount, _rwp.hasActiveCommissionerAuction, _rwp.mintingEnabled) =  leagueRosterContract.realWorldPlayerFromIndex(_rosterIndex);

        // Mint this player, sending it to the message sender
        _mintPlayer(uint32(_rosterIndex), _rwp.mintedCount, msg.sender);

        // Finally, update our realWorldPlayer record to reflect the fact that we just
        // minted a new one, and there is an active commish auction. The only portion of
        // the RWP record we change here is an update to the mingedCount.
        leagueRosterContract.updateRealWorldPlayer(uint32(_rosterIndex), _rwp.prevCommissionerSalePrice, uint64(now), _rwp.mintedCount + 1, _rwp.hasActiveCommissionerAuction, _rwp.mintingEnabled);

        emit MarketingTokenRedeemed(keyWordsHash, _rwp.md5Token, msg.sender);
      }

    }
  }
/// @dev Allows anyone to try to redeem a marketing token by passing N words that will
/// be SHA256'ed to match an entry in our marketingTokens mapping. If a match is found,
/// a CryptoSports token is created that corresponds to the md5 retrieved
/// from the marketingTokens mapping and its owner is assigned as the msg.sender.
///
/// ANYONE CAN REDEEM A MARKETING token
///
/// @param keyWords - A string that will keccak256 to an entry in the marketingTokens mapping