// Contract: TokenHolder
// Target function: function getCommission(uint _amount) public view returns(uint)
 }

    /**
    * @dev Add given amount to the user's balance
    * @param _user address of user
    * @param _amount uint representing value in Wei to be added
    */
    function add(address _user, uint _amount) public payable onlyGames returns(bool) {
        return _add(_user, _amount);
    }

    /**
    * @dev Add given amount to the user's balance
    * @param _user address of user
    * @param _amount uint representing value in Wei to be added
    */
    function _add(address _user, uint _amount) internal returns(bool) {
        if (_amount == 0) return false;

        userBalances[_user] = userBalances[_user].add(_amount);
        totalPlayerBalances = totalPlayerBalances.add(_amount);

        require(totalPlayerBalances <= address(this).balance);

        return true;
    }

    /**
    * @dev Add given bonus amount to the user's balance
    * @dev Only operators can add bonus amount
    * @param _user address of user
    * @param _amount uint representing value in Wei to be added
    */
    function addBonus(address _user, uint _amount) public onlyOperators payable returns(bool) {
        return _addBonus(_user, _amount);
    }

    /**
    * @dev Add given bonus amount to the user's balance
    * @dev Only operators can add bonus amount
    * @param _user address of user
    * @param _amount uint representing value in Wei to be added
    */
    function _addBonus(address _user, uint _amount) internal returns(bool) {
        if (_amount == 0) return false;

        userBonusBalances[_user] = userBonusBalances[_user].add(_amount);

        return true;
    }

    /**
    * @dev Subtract bonus amount from user's balance
    * @param _user address of user
    * @param _amount uint representing value in Wei to be subtracted
    */
    function subtractBonus(address _user, uint _amount) public onlyGames returns(bool) {
        return _subtractBonus(_user, _amount);
    }

    /**
    * @dev Subtract bonus amount from user's balance
    * @param _user address of user
    * @param _amount uint representing value in Wei to be subtracted
    */
    function _subtractBonus(address _user, uint _amount) internal returns(bool) {
        require(tx.origin == _user);

        if (userBonusBalances[_user] < _amount) return false;

        userBonusBalances[_user] = userBonusBalances[_user].sub(_amount);

        return true;
    }

    /**
    * @dev Allows users to make withdrawals
    * @param _user address of user which ETH should be sent to
    * @param _amount Amount to be withdrawn
    */
    function withdraw(address payable _user, uint _amount) public {
        // Any game can implement its own withdrawal method
        require(_user == msg.sender);

        BadBitCasino(address(this))._finalizeLastBets(_user);

        // @dev This will not pass if _amount is bigger than available balance
        userBalances[_user] = userBalances[_user].sub(_amount);
        totalPlayerBalances = totalPlayerBalances.sub(_amount);

        uint _timestamp = now;
        userWithdrawals[_user].push(Withdrawal({
            withdrawalTimestamp: _timestamp,
            amount: _amount
        }));

        _user.transfer(_amount);

        emit UserWithdrawal(_user, _timestamp);
    }

    function getNumberOfUserWithdrawals(address _user) public view returns(uint) {
        return userWithdrawals[_user].length;
    }

    function addAmountByDistributor(address _user, uint _amount) public {
        require(msg.sender == distributorAddress);

        _add(_user, _amount);
    }

    /**
    * @dev Allows anyone to just send ether to the contract
    */
    function() external payable {
        totalFundsSentByOwner += msg.value;
    }
}

contract BadBitCasino is usingOraclize, BadBitWallet {

    using SafeMath for uint256;

    struct Bet {
        address game;
        uint betId;
    }

    struct DonBet {
        address game;
        address sender;
        uint amount;
        bool won;
        bool finalized;
    }

    /**
    * @dev keep total funds spent on oraclize
    */
    uint public totalFundsSpentOnOraclize;
    /**
    * @dev keep total commission earned for DoN games
    */
    uint public commissionEarned;

    /**
    * @dev keep how many ether has been paid out as profit to all players
    */
    uint public totalPlayerProfits;

    /**
    * @dev keep how many ethers is lost by all players
    */
    uint public totalFundsLostByPlayers;
    /**
    * @dev keep track of all funds sent directly to contract
    */
    uint public totalFundsSentByOwner;

    /**
    * @dev keep track of all funds that have been transfered from revenue pool to initial deposit pool
    */
    uint public totalFundsTransferredToDepositPool;

    /**
    * @dev keep how much ether has been won by each player (this includes the original bet amount)
    */
    mapping(address => uint) public totalWinningsForPlayer;
    /**
    * @dev keep current level for each player
    */
    mapping(address => uint) public playerLevel;
    /**
    * @dev keep amount of cumulative ETH won required for each level
    */
    uint[] public amountRequiredForLevel;
    /**
    * @dev Holds all bets of user with specific address
    */
    mapping(address => Bet[]) public bets;
    /**
    * @dev Keeps track of sender for queryId when playing doubleOrNothing
    */
    mapping(bytes32 => DonBet) public donBets;
    /**
    * @dev All users that played game
    */
    address[] public users;
    /**
    * @dev Mapping that keep first unfinalized bet for user
    */
    mapping(address => uint) public firstUnfinalizedBet;
    /**
    * @dev Tracks last won amount per games for each user
    */
    mapping(address => mapping(address => uint)) public lastWonAmountPerGame;

    event BigWin(address indexed user, address indexed game, uint amount);
    event DoubleOrNothingPlayed(bytes32 queryId, address indexed user, uint indexed betId, address indexed game);
    event DoubleOrNothingFinished(bytes32 indexed queryId, address indexed user, bool won, uint tokensWon);

    modifier onlyGames() {
        require (settings.isGameAddress(msg.sender));
        _;
    }

    modifier onlyOperators() {
        require (settings.isOperatorAddress(msg.sender));
        _;
    }

    constructor(address _settings) BadBitWallet(_settings) public {

        oraclize_setNetwork();
        // set gas price to 1 gwei
        oraclize_setCustomGasPrice(10000000000);
    }

    function fillAmountRequiredForLevel() public {
        amountRequiredForLevel = [0, 3 ether, 9 ether, 18 ether, 30 ether,
            45 ether, 63 ether, 84 ether, 108 ether, 135 ether, 165 ether,
            198 ether, 234 ether, 273 ether, 315 ether, 360 ether, 408 ether,
            459 ether, 513 ether, 570 ether, 630 ether, 693 ether, 759 ether,
            828 ether, 900 ether, 975 ether, 1053 ether, 1134 ether, 1218 ether,
            1305 ether, 1395 ether, 1488 ether, 1584 ether, 1683 ether, 1785 ether,
            1890 ether, 1998 ether, 2109 ether, 2223 ether, 2340 ether, 2460 ether,
            2583 ether, 2709 ether, 2838 ether, 2970 ether, 3105 ether, 3243 ether,
            3384 ether, 3528 ether, 3675 ether, 3825 ether, 3978 ether, 4134 ether,
            4293 ether, 4455 ether, 4620 ether, 4788 ether, 4959 ether, 5133 ether,
            5310 ether, 5490 ether, 5673 ether, 5859 ether, 6048 ether, 6240 ether,
            6435 ether, 6633 ether, 6834 ether, 7038 ether, 7245 ether, 7455 ether,
            7668 ether, 7884 ether, 8103 ether, 8325 ether, 8550 ether, 8778 ether,
            9009 ether, 9243 ether, 9480 ether, 9720 ether, 9963 ether, 10209 ether,
            10458 ether, 10710 ether, 10965 ether, 11223 ether, 11484 ether,
            11748 ether, 12015 ether, 12285 ether, 12558 ether, 12834 ether,
            13113 ether, 13395 ether, 13680 ether, 13968 ether, 14259 ether,
            14553 ether, 14850 ether, 15150 ether];
    }

    function placeBet(address _user, uint _betId, uint _amount, bool bonus) public onlyGames {
        require(settings.BETS_ALLOWED());

        if (bets[_user].length == 0) {
            users.push(_user);
        }

        _finalizeLastBets(_user);

        if (bonus) {
            require(_subtractBonus(_user, _amount));
        } else {
            require(_subtract(_user, _amount));
        }

        bets[_user].push(Bet({
                game: msg.sender,
                betId: _betId
            }));

        // if bets needs to be finalzied, we don't do anything, but if all bets are finalized, we move pointer to new bet
        // check firstUnfinalizedBet[_user] != _betId because if that is the case we can't get state of bet from other contract
        // as its still not written there
        if (firstUnfinalizedBet[_user] != _betId && !needToUpdateBetsForUser(_user)) {
            firstUnfinalizedBet[_user] = bets[_user].length - 1;
        }
    }

    function _finalizeLastBets(address _user) public {
        uint count = bets[_user].length;

        if (count > 0 && needToUpdateBetsForUser(_user)) {
            uint starting = firstUnfinalizedBet[_user];

            for (uint i=starting; i<count; i++) {

                Bet memory betObject = bets[_user][i];
                bool finalized;
                bool isBonus;
                (, finalized, , isBonus) = GameInterface(betObject.game).getBetInfo(_user, betObject.betId);

                if (!finalized) {
                    if (GameInterface(betObject.game).canFinalizeBet(_user, betObject.betId)) {
                        (uint profit, uint totalWon) = GameInterface(betObject.game).finalizeBet(_user, betObject.betId);

                        if (totalWon > 0) {
                            require(_add(_user, totalWon));
                            totalPlayerProfits += isBonus ? totalWon : profit;
                            totalWinningsForPlayer[_user] += totalWon;
                            updatePlayerLevelIfNeeded(_user);
                            lastWonAmountPerGame[betObject.game][_user] = totalWon;

                            if (totalWon > settings.BIG_WIN_THRESHOLD()) {
                                emit BigWin(_user, betObject.game, totalWon);
                            }
                        }
                    } else {
                        if (starting == firstUnfinalizedBet[_user]) {
                            bool isFirstFinalized;
                            // inside this if so we don't call this all the time if not needed
                            (, isFirstFinalized, ,) = GameInterface(bets[_user][starting].game).getBetInfo(_user, bets[_user][starting].betId);

                            if (isFirstFinalized) {
                                firstUnfinalizedBet[_user] = i;
                            }
                        }
                    }
                }
            }
        }
    }

    /**
    * @dev Allows owner to finalizeLastBets for array of addresses
    * @dev This is added because owner can't withdraw ether without all bets updated
    */
    function finalizeLastBetsForAddresses(address[] memory _addresses) public onlyOperators {
        for (uint i=0; i<_addresses.length; i++) {
            _finalizeLastBets(_addresses[i]);
        }
    }

    function placeBetForDoubleOrNothing(address _game, uint _amount) public {
        require(settings.BETS_ALLOWED());

        _finalizeLastBets(msg.sender);

        // needs to be after finalizeLastBets is executed
        require(lastWonAmountPerGame[_game][msg.sender] >= _amount);

        uint _gasPrice = tx.gasprice;
        // @dev we don't want to revert if gasPrice is not valid, we just set it to MIN or MAX
        if (_gasPrice < settings.MIN_GAS_PRICE()) {
            _gasPrice = settings.MIN_GAS_PRICE();
        } else if (_gasPrice > settings.MAX_GAS_PRICE()) {
            _gasPrice = settings.MAX_GAS_PRICE();
        }

        oraclize_setCustomGasPrice(_gasPrice);
        uint gasLimit = settings.ORACLIZE_GAS_LIMIT();

        bytes32 queryId = settings.USE_BLOCKHASH_RANDOM_SEED() ? oraclize_query(0, "URL", "", gasLimit) : oraclize_newRandomDSQuery(0, 8, gasLimit);

        totalFundsSpentOnOraclize += settings.USE_BLOCKHASH_RANDOM_SEED() ? oraclize_getPrice("url", gasLimit) : oraclize_getPrice("random", gasLimit);

        donBets[queryId] = DonBet({
            game: _game,
            sender: msg.sender,
            amount: _amount,
            won: false,
            finalized: false
        });

        require(_subtract(msg.sender, _amount));

        emit DoubleOrNothingPlayed(queryId, msg.sender, GameInterface(_game).getTotalBets(msg.sender) - 1, _game);
    }

    function __callback(bytes32 myid, string memory result) public {
        if (msg.sender != oraclize_cbAddress() && !settings.isOperatorAddress(msg.sender)) revert();

        // @dev Oraclize sometimes rebroadcast transactions, so we need to make sure thats not the case
        require(!donBets[myid].finalized);
        require(settings.USE_BLOCKHASH_RANDOM_SEED() || bytes(result)[0] != 0);

        uint randomNumber;

        if(settings.isOperatorAddress(msg.sender) || settings.USE_BLOCKHASH_RANDOM_SEED()) {
            randomNumber = uint224(uint(blockhash(block.number - 1)).mod(100));
        } else {
            randomNumber = uint224(uint(keccak256(abi.encodePacked(result))).mod(100));
        }

        // @dev this means user won
        if (randomNumber < 50) {
            commissionEarned += getCommission(donBets[myid].amount);
            uint winnings = getPossibleWinnings(donBets[myid].amount);
            uint wonAmount = donBets[myid].amount + winnings;

            totalWinningsForPlayer[donBets[myid].sender] += winnings;
            updatePlayerLevelIfNeeded(donBets[myid].sender);

            require(_add(donBets[myid].sender, wonAmount));
            lastWonAmountPerGame[donBets[myid].game][donBets[myid].sender] = wonAmount;
            donBets[myid].won = true;
        } else {
            totalFundsLostByPlayers += donBets[myid].amount;
        }

        uint tokensWon = 0;

        uint[] memory betSizes = new uint[](1);
        betSizes[0] = donBets[myid].amount;
        uint[] memory chances = new uint[](1);
        chances[0] = 50;

        if (distributor.shouldWinTokens(donBets[myid].game, keccak256(abi.encodePacked(result)), donBets[myid].sender, betSizes, chances, 1)) {
            distributor.winTokens(donBets[myid].sender);
            tokensWon = distributor.getStandardLot();
        }

        emit DoubleOrNothingFinished(myid, donBets[myid].sender, randomNumber < 50, tokensWon);

        donBets[myid].finalized = true;
    }

    /**
    * @dev Calculate current user balance, taking into account unfinalized bets
    * @param _user address of the user
    * @return returns uint representing user balance in wei
    */
    function getCurrentBalance(address _user) public view returns(uint) {
        uint balance = userBalances[_user];
        uint count = bets[_user].length;

        if (count == 0) {
            return balance;
        }

        if (needToUpdateBetsForUser(_user)) {
            uint starting = firstUnfinalizedBet[_user];

            for (uint i=starting; i<count; i++) {
                Bet memory betObject = bets[_user][i];
                (uint amount, bool finalized, ,) = GameInterface(betObject.game).getBetInfo(_user, betObject.betId);

                if (!finalized) {
                    uint winnings = GameInterface(betObject.game).getUserProfitForFinishedBet(_user, betObject.betId);

                    if(winnings > 0) {
                        balance = balance + amount + winnings;
                    }
                }
            }
        }

        return balance;
    }