pragma solidity 0.4.25;

/*
 * 0xBtcnnRoll.
 */

contract BTCNNInterface {
  function getFrontEndTokenBalanceOf(address who) public view returns(uint);

  function transfer(address _to, uint _value) public returns(bool);

  function approve(address spender, uint tokens) public returns(bool);
}

contract BtcnnRoll {
  using SafeMath
  for uint;

  // Makes sure that player profit can't exceed a maximum amount,
  //  that the bet size is valid, and the playerNumber is in range.
  modifier betIsValid(uint _betSize, uint _playerNumber) {
    require(calculateProfit(_betSize, _playerNumber) < maxProfit &&
      _betSize >= minBet &&
      _playerNumber > minNumber &&
      _playerNumber < maxNumber);
    _;
  }

  // Requires game to be currently active
  modifier gameIsActive {
    require(gamePaused == false);
    _;
  }

  // Requires msg.sender to be owner
  modifier onlyOwner {
    require(msg.sender == owner);
    _;
  }

  // Constants
  uint constant private MAX_INT = 2 ** 256 - 1;
  uint constant public maxProfitDivisor = 1000000;
  uint constant public maxNumber = 99;
  uint constant public minNumber = 2;
  uint constant public houseEdgeDivisor = 1000;

  // Configurables
  bool public gamePaused;

  address public owner;
  address public BTCNNBankroll;
  address public BTCNNTKNADDR;

  BTCNNInterface public BTCNNTKN;

  uint public contractBalance;
  uint public houseEdge;
  uint public maxProfit;
  uint public maxProfitAsPercentOfHouse;
  uint public minBet = 0;

  // Trackers
  uint public totalBets;
  uint public totalBTCNNWagered;

  // Events

  // Logs bets + output to web3 for precise 'payout on win' field in UI
  event LogBet(address sender, uint value, uint rollUnder);

  // Outputs to web3 UI on bet result
  // Status: 0=lose, 1=win, 2=win + failed send, 3=refund, 4=refund + failed send
  event LogResult(address indexed player, uint result, uint rollUnder, uint profit, uint tokensBetted, bool won);

  // Logs owner transfers
  event LogOwnerTransfer(address indexed SentToAddress, uint indexed AmountTransferred);

  // Logs changes in maximum profit
  event MaxProfitChanged(uint _oldMaxProfit, uint _newMaxProfit);

  // Logs current contract balance
  event CurrentContractBalance(uint _tokens);

  constructor(address btcnntknaddr, address btcnnbankrolladdr) public {
    // Owner is deployer
    owner = msg.sender;

    // Initialize the BTCNN contract and bankroll interfaces
    BTCNNTKN = BTCNNInterface(btcnntknaddr);
    BTCNNTKNADDR = btcnntknaddr;

    // Set the bankroll
    BTCNNBankroll = btcnnbankrolladdr;

    // Init 990 = 99% (1% houseEdge)
    houseEdge = 990;

    // The maximum profit from each bet is 10% of the contract balance.
    ownerSetMaxProfitAsPercentOfHouse(10000);

    // Init min bet (1 BTCNN)
    ownerSetMinBet(1e18);

    // Allow 'unlimited' token transfer by the bankroll
    BTCNNTKN.approve(btcnnbankrolladdr, MAX_INT);
  }

  function () public {
    revert();
  }

  // Returns a random number using a specified block number
  // Always use a FUTURE block number.
  function maxRandom(uint blockn, address entropy) public view returns(uint256 randomNumber) {
    return uint256(keccak256(
      abi.encodePacked(
        blockhash(blockn),
        entropy)
    ));
  }

  // Random helper
  function random(uint256 upper, uint256 blockn, address entropy) internal view returns(uint256 randomNumber) {
    return maxRandom(blockn, entropy) % upper;
  }

  // Calculate the maximum potential profit
  function calculateProfit(uint _initBet, uint _roll)
  private
  view
  returns(uint) {
    return ((((_initBet * (100 - (_roll.sub(1)))) / (_roll.sub(1)) + _initBet)) * houseEdge / houseEdgeDivisor) - _initBet;
  }

  // I present a struct which takes only 20k gas
  struct playerRoll {
    uint200 tokenValue; // Token value in uint
    uint48 blockn; // Block number 48 bits
    uint8 rollUnder; // Roll under 8 bits
  }

  // Mapping because a player can do one roll at a time
  mapping(address => playerRoll) public playerRolls;

  function _playerRollDice(uint _rollUnder, TKN _tkn) private
  gameIsActive
  betIsValid(_tkn.value, _rollUnder) {
    require(_tkn.value < ((2 ** 200) - 1)); // Smaller than the storage of 1 uint200;
    require(block.number < ((2 ** 48) - 1)); // Current block number smaller than storage of 1 uint48

    // Note that msg.sender is the Token Contract Address
    // and "_from" is the sender of the tokens

    // Check that this is a BTCNN token transfer
    require(_btcnnToken(msg.sender));

    playerRoll memory roll = playerRolls[_tkn.sender];

    // Cannot bet twice in one block
    require(block.number != roll.blockn);

    // If there exists a roll, finish it
    if (roll.blockn != 0) {
      _finishBet(false, _tkn.sender);
    }

    // Set struct block number, token value, and rollUnder values
    roll.blockn = uint48(block.number);
    roll.tokenValue = uint200(_tkn.value);
    roll.rollUnder = uint8(_rollUnder);

    // Store the roll struct - 20k gas.
    playerRolls[_tkn.sender] = roll;

    // Provides accurate numbers for web3 and allows for manual refunds
    emit LogBet(_tkn.sender, _tkn.value, _rollUnder);

    // Increment total number of bets
    totalBets += 1;

    // Total wagered
    totalBTCNNWagered += _tkn.value;
  }

  // Finished the current bet of a player, if they have one
  function finishBet() public
  gameIsActive
  returns(uint) {
    return _finishBet(true, msg.sender);
  }

  /*
   * Pay winner, update contract balance
   * to calculate new max bet, and send reward.
   */
  function _finishBet(bool delete_it, address target) private returns(uint) {
    playerRoll memory roll = playerRolls[target];
    require(roll.tokenValue > 0); // No re-entracy
    require(roll.blockn != block.number);
    // If the block is more than 255 blocks old, we can't get the result
    // Also, if the result has already happened, fail as well
    uint result;
    if (block.number - roll.blockn > 255) {
      result = 1000; // Cant win
    } else {
      // Grab the result - random based ONLY on a past block (future when submitted)
      result = random(99, roll.blockn, target) + 1;
    }

    uint rollUnder = roll.rollUnder;

    if (result < rollUnder) {
      // Player has won!

      // Safely map player profit
      uint profit = calculateProfit(roll.tokenValue, rollUnder);

      if (profit > maxProfit) {
        profit = maxProfit;
      }

      // Safely reduce contract balance by player profit
      contractBalance = contractBalance.sub(profit);

      emit LogResult(target, result, rollUnder, profit, roll.tokenValue, true);

      // Update maximum profit
      setMaxProfit();


      // Prevent re-entracy memes
      playerRolls[target] = playerRoll(uint200(0), uint48(0), uint8(0));


      // Transfer profit plus original bet
      BTCNNTKN.transfer(target, profit + roll.tokenValue);

      return result;

    } else {
      /*
       * Player has lost
       * Update contract balance to calculate new max bet
       */
      emit LogResult(target, result, rollUnder, profit, roll.tokenValue, false);

      /*
       *  Safely adjust contractBalance
       *  SetMaxProfit
       */
      contractBalance = contractBalance.add(roll.tokenValue);

      playerRolls[target] = playerRoll(uint200(0), uint48(0), uint8(0));
      // No need to actually delete player roll here since player ALWAYS loses
      // Saves gas on next buy

      // Update maximum profit
      setMaxProfit();

      return result;
    }
  }

  // TKN struct
  struct TKN {
    address sender;
    uint value;
  }

  // Token fallback to bet or deposit from bankroll
  function tokenFallback(address _from, uint _value, bytes _data) public returns(bool) {
    require(msg.sender == BTCNNTKNADDR);
    if (_from == BTCNNBankroll) {
      // Update the contract balance
      contractBalance = contractBalance.add(_value);

      // Update the maximum profit
      uint oldMaxProfit = maxProfit;
      setMaxProfit();

      emit MaxProfitChanged(oldMaxProfit, maxProfit);
      return true;

    } else {
      TKN memory _tkn;
      _tkn.sender = _from;
      _tkn.value = _value;
      uint8 chosenNumber = uint8(_data[0]);
      _playerRollDice(chosenNumber, _tkn);
    }

    return true;
  }

  /*
   * Sets max profit
   */
  function setMaxProfit() internal {
    emit CurrentContractBalance(contractBalance);
    maxProfit = (contractBalance * maxProfitAsPercentOfHouse) / maxProfitDivisor;
  }

  // Only owner adjust contract balance variable (only used for max profit calc)
  function ownerUpdateContractBalance(uint newContractBalance) public
  onlyOwner {
    contractBalance = newContractBalance;
  }

  // Only owner address can set maxProfitAsPercentOfHouse
  function ownerSetMaxProfitAsPercentOfHouse(uint newMaxProfitAsPercent) public
  onlyOwner {
    // Restricts each bet to a maximum profit of 20% contractBalance
    require(newMaxProfitAsPercent <= 200000);
    maxProfitAsPercentOfHouse = newMaxProfitAsPercent;
    setMaxProfit();
  }

  // Only owner address can set minBet
  function ownerSetMinBet(uint newMinimumBet) public
  onlyOwner {
    minBet = newMinimumBet;
  }

  // Only owner address can transfer BTCNN
  function ownerTransferBTCNN(address sendTo, uint amount) public
  onlyOwner {
    // Safely update contract balance when sending out funds
    contractBalance = contractBalance.sub(amount);

    // update max profit
    setMaxProfit();
    require(BTCNNTKN.transfer(sendTo, amount));
    emit LogOwnerTransfer(sendTo, amount);
  }

  // Only owner address can set emergency pause #1
  function ownerPauseGame(bool newStatus) public
  onlyOwner {
    gamePaused = newStatus;
  }

  // Only owner address can set bankroll address
  function ownerSetBankroll(address newBankroll) public
  onlyOwner {
    BTCNNTKN.approve(BTCNNBankroll, 0);
    BTCNNBankroll = newBankroll;
    BTCNNTKN.approve(newBankroll, MAX_INT);
  }

  // Only owner address can set owner address
  function ownerChangeOwner(address newOwner) public
  onlyOwner {
    owner = newOwner;
  }

  // Only owner address can selfdestruct - emergency
  function ownerkill() public
  onlyOwner {
    BTCNNTKN.transfer(owner, contractBalance);
    selfdestruct(owner);
  }

  function dumpdivs() public {

    BTCNNTKN.transfer(BTCNNBankroll, BTCNNTKN.getFrontEndTokenBalanceOf(this));
  }

  function _btcnnToken(address _tokenContract) private view returns(bool) {
    return _tokenContract == BTCNNTKNADDR;
    // Is this the BTCNN token contract?
  }
}

/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {

  /**
   * @dev Multiplies two numbers, throws on overflow.
   */
  function mul(uint a, uint b) internal pure returns(uint) {
    if (a == 0) {
      return 0;
    }
    uint c = a * b;
    assert(c / a == b);
    return c;
  }

  /**
   * @dev Integer division of two numbers, truncating the quotient.
   */
  function div(uint a, uint b) internal pure returns(uint) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  /**
   * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
   */
  function sub(uint a, uint b) internal pure returns(uint) {
    assert(b <= a);
    return a - b;
  }

  /**
   * @dev Adds two numbers, throws on overflow.
   */
  function add(uint a, uint b) internal pure returns(uint) {
    uint c = a + b;
    assert(c >= a);
    return c;
  }
}