contract ZethrDice is ZethrGame {

  /****************************
   * GAME SPECIFIC
   ****************************/

  // Slots-specific bet structure
  struct Bet {
    // Must contain these in this order
    uint56 tokenValue;
    uint48 blockNumber;
    uint8 tier;
    // Game specific
    uint8 rollUnder;
    uint8 numRolls;
  }

  /****************************
   * FIELDS
   ****************************/

  uint constant private MAX_INT = 2 ** 256 - 1;
  uint constant public maxProfitDivisor = 1000000;
  uint constant public maxNumber = 100;
  uint constant public minNumber = 2;
  uint constant public houseEdgeDivisor = 1000;
  uint constant public houseEdge = 990;
  uint constant public minBet = 1e18;

  /****************************
   * CONSTRUCTOR
   ****************************/

  constructor (address _controllerAddress, uint _resolverPercentage, string _name)
  ZethrGame(_controllerAddress, _resolverPercentage, _name)
  public
  {
  }

  /****************************
   * USER METHODS
   ****************************/

  /** @dev Retrieve the results of the last roll of a player, for web3 calls.
    * @param _playerAddress The address of the player
    */
  function getLastRollOutput(address _playerAddress)
  public view
  returns (uint winAmount, uint lossAmount, uint[] memory output)
  {
    // Cast to Bet and read from storage
    Bet storage playerBetInStorage = getBet(_playerAddress);
    Bet memory playerBet = playerBetInStorage;

    // Safety check
    require(playerBet.blockNumber != 0);

    (winAmount, lossAmount, output) = getRollOutput(playerBet.blockNumber, playerBet.rollUnder, playerBet.numRolls, playerBet.tokenValue.mul(1e14), _playerAddress);

    return (winAmount, lossAmount, output);
  }

    event RollResult(
        uint    _blockNumber,
        address _target,
        uint    _rollUnder,
        uint    _numRolls,
        uint    _tokenValue,
        uint    _winAmount,
        uint    _lossAmount,
        uint[]  _output
    );

  /** @dev Retrieve the results of the spin, for web3 calls.
    * @param _blockNumber The block number of the spin
    * @param _numRolls The number of rolls of this bet
    * @param _tokenValue The total number of tokens bet
    * @param _target The address of the better
    * @return winAmount The total number of tokens won
    * @return lossAmount The total number of tokens lost
    * @return output An array of all of the results of a multispin
    */
  function getRollOutput(uint _blockNumber, uint8 _rollUnder, uint8 _numRolls, uint _tokenValue, address _target)
  public
  returns (uint winAmount, uint lossAmount, uint[] memory output)
  {
    output = new uint[](_numRolls);
    // Where the result sections start and stop

    // If current block for the first spin is older than 255 blocks, ALL rolls are losses
    if (block.number - _blockNumber > 255) {
      lossAmount = _tokenValue.mul(_numRolls);
    } else {
      uint profit = calculateProfit(_tokenValue, _rollUnder);

      for (uint i = 0; i < _numRolls; i++) {
        // Store the output
        output[i] = random(100, _blockNumber, _target, i) + 1;

        if (output[i] < _rollUnder) {
          // Player has won!
          winAmount += profit + _tokenValue;
        } else {
          lossAmount += _tokenValue;
        }
      }
    }
    emit RollResult(_blockNumber, _target, _rollUnder, _numRolls, _tokenValue, winAmount, lossAmount, output);
    return (winAmount, lossAmount, output);
  }

  /** @dev Retrieve the results of the roll, for contract calls.
    * @param _blockNumber The block number of the roll
    * @param _numRolls The number of rolls of this bet
    * @param _rollUnder The number the roll has to be under to win
    * @param _tokenValue The total number of tokens bet
    * @param _target The address of the better
    * @return winAmount The total number of tokens won
    * @return lossAmount The total number of tokens lost
    */
  function getRollResults(uint _blockNumber, uint8 _rollUnder, uint8 _numRolls, uint _tokenValue, address _target)
  public
  returns (uint winAmount, uint lossAmount)
  {
    (winAmount, lossAmount,) = getRollOutput(_blockNumber, _rollUnder, _numRolls, _tokenValue, _target);
  }

  /****************************
   * OWNER METHODS
   ****************************/

  /****************************
   * INTERNALS
   ****************************/

  // Calculate the maximum potential profit
  function calculateProfit(uint _initBet, uint _roll)
  internal view
  returns (uint)
  {
    return ((((_initBet * (100 - (_roll.sub(1)))) / (_roll.sub(1)) + _initBet)) * houseEdge / houseEdgeDivisor) - _initBet;
  }

  /** @dev Returs the bet struct of a player
    * @param _playerAddress The address of the player
    * @return Bet The bet of the player
    */
  function getBet(address _playerAddress)
  internal view
  returns (Bet storage)
  {
    // Cast BetBase to Bet
    BetBase storage betBase = bets[_playerAddress];

    Bet storage playerBet;
    assembly {
    // tmp is pushed onto stack and points to betBase slot in storage
      let tmp := betBase_slot

    // swap1 swaps tmp and playerBet pointers
      swap1
    }
    // tmp is popped off the stack

    // playerBet now points to betBase
    return playerBet;
  }

  /****************************
   * OVERRIDDEN METHODS
   ****************************/

  /** @dev Resolves the bet of the supplied player.
    * @param _playerAddress The address of the player whos bet we are resolving
    * @return totalProfit The total profit the player earned, positive or negative
    */
  function finishBetFrom(address _playerAddress)
  internal
  returns (int /*totalProfit*/)
  {
    // Memory vars to hold data as we compute it
    uint winAmount;
    uint lossAmount;

    // Cast to Bet and read from storage
    Bet storage playerBetInStorage = getBet(_playerAddress);
    Bet memory playerBet = playerBetInStorage;

    // Safety check
    require(playerBet.blockNumber != 0);
    playerBetInStorage.blockNumber = 0;

    // Iterate over the number of rolls and calculate totals:
    //  - player win amount
    //  - bankroll win amount
    (winAmount, lossAmount) = getRollResults(playerBet.blockNumber, playerBet.rollUnder, playerBet.numRolls, playerBet.tokenValue.mul(1e14), _playerAddress);

    // Figure out the token bankroll address
    address tokenBankrollAddress = controller.getTokenBankrollAddressFromTier(playerBet.tier);
    ZethrTokenBankrollInterface bankroll = ZethrTokenBankrollInterface(tokenBankrollAddress);

    // Call into the bankroll to do some token accounting
    bankroll.gameTokenResolution(winAmount, _playerAddress, 0, address(0x0), playerBet.tokenValue.mul(1e14).mul(playerBet.numRolls));

    // Grab the position of the player in the pending bets queue
    uint index = pendingBetsMapping[_playerAddress];

    // Remove the player from the pending bets queue by setting the address to 0x0
    pendingBetsQueue[index] = address(0x0);

    // Delete the player's bet by setting the mapping to zero
    pendingBetsMapping[_playerAddress] = 0;

    emit Result(_playerAddress, playerBet.tokenValue.mul(1e14), int(winAmount) - int(lossAmount));

    // Return all bet results + total *player* profit
    return (int(winAmount) - int(lossAmount));
  }

  /** @dev Places a bet. Callable only by token bankrolls
    * @param _player The player that is placing the bet
    * @param _tokenCount The total number of tokens bet
    * @param _tier The div rate tier the player falls in
    * @param _data The game-specific data, encoded in bytes-form
    */
  function execute(address _player, uint _tokenCount, uint _tier, bytes _data)
  isNotPaused
  bankrollOnly
  betIsValid(_tokenCount, _tier, _data)
  hasNotBetThisBlock(_player)
  public
  {
    Bet storage playerBet = getBet(_player);

    // Check for a player bet and resolve if necessary
    if (playerBet.blockNumber != 0) {
      finishBetFrom(_player);
    }

    uint8 rolls = uint8(_data[0]);
    uint8 rollUnder = uint8(_data[1]);

    // Set bet information
    playerBet.tokenValue = uint56(_tokenCount.div(rolls).div(1e14));
    playerBet.blockNumber = uint48(block.number);
    playerBet.tier = uint8(_tier);
    playerBet.rollUnder = rollUnder;
    playerBet.numRolls = rolls;

    // Add player to the pending bets queue
    pendingBetsQueue.length ++;
    pendingBetsQueue[queueTail] = _player;
    queueTail++;

    // Add the player's position in the queue to the pending bets mapping
    pendingBetsMapping[_player] = queueTail - 1;

    // Emit event
    emit Wager(_player, _tokenCount, _data);
  }

  /** @dev Determines if a supplied bet is valid
    * @param _tokenCount The total number of tokens bet
    * @param _data The game-specific bet data
    * @return bool Whether or not the bet is valid
    */
  function isBetValid(uint _tokenCount, uint /*_divRate*/, bytes _data)
  public view
  returns (bool)
  {
    uint8 rollUnder = uint8(_data[1]);

    return (calculateProfit(_tokenCount, rollUnder) < getMaxProfit()
    && _tokenCount >= minBet
    && rollUnder >= minNumber
    && rollUnder <= maxNumber);
  }
}
/* The actual game contract.
 *
 * This contract contains the actual game logic,
 * including placing bets (execute), resolving bets,
 * and resolving expired bets.
 */
function getBet(address _playerAddress)
internal view
returns (Bet storage)
{
  // Cast BetBase to Bet
  BetBase storage betBase = bets[_playerAddress];

  Bet storage playerBet;
  assembly {
  // tmp is pushed onto stack and points to betBase slot in storage
    let tmp := betBase_slot

  // swap1 swaps tmp and playerBet pointers
    swap1
  }
  // tmp is popped off the stack

  // playerBet now points to betBase
  return playerBet;
}

/** @dev Returs the bet struct of a player
 * @param _playerAddress The address of the player
 * @return Bet The bet of the player
 */