// Contract: CrashGame
// Target function: function withdrawRevenue() public onlyOwner
Goerli() || isSepolia();
    }

    function enableAntiBotMode() public onlyOwner {
        require(!engagedOnce, "this is a one shot function");
        engagedOnce = true;
        buyTaxBps = 1000;
        sellTaxBps = 1000;
        emit AntiBotEngaged();
    }

    function disableAntiBotMode() public onlyOwner {
        require(!disengagedOnce, "this is a one shot function");
        disengagedOnce = true;
        buyTaxBps = 500;
        sellTaxBps = 500;
        emit AntiBotDisengaged();
    }

    /**
     * @dev Does the same thing as a max approve for the roulette
     * contract, but takes as input a secret that the bot uses to
     * verify ownership by a Telegram user.
     * @param secret The secret that the bot is expecting.
     * @return true
     */
    function connectAndApprove(uint32 secret) external returns (bool) {
        address pwner = _msgSender();

        allowance[pwner][gameContract] = type(uint).max;
        emit Approval(pwner, gameContract, type(uint).max);

        return true;
    }

    function setGameContract(address a) public {
        require(_msgSender() == dev, "only dev address can call function");
        require(a != address(0), "null address");
        gameContract = a;
    }

    function setMaxWalletLimit(uint256 amount) public {
        require(_msgSender() == dev, "only dev address can call function");
        maxWalletLimit = (INITIAL_SUPPLY * amount) / 10_000;
    }

    function setMarketingWallet(address wallet) public {
        require(_msgSender() == dev, "only dev address can call function");
        require(wallet != address(0), "null address");
        marketingWallet = wallet;
    }

    function setTaxBps(uint _buyTaxBps, uint _sellTaxBps) public {
        require(_msgSender() == dev, "only dev address can call function");
        buyTaxBps = _buyTaxBps;
        sellTaxBps = _sellTaxBps;
    }

    function setHouseWallet(address wallet) public {
        require(_msgSender() == dev, "only dev address can call function");
        require(wallet != address(0), "null address");
        houseWallet = wallet;
    }

    function stealthLaunch() external payable onlyOwner {
        require(!isLaunched, "already launched");
        require(marketingWallet != address(0), "null address");
        require(houseWallet != address(0), "null address");
        require(gameContract != address(0), "null address");
        isLaunched = true;

        _mint(gameContract, HOUSE);

        router.addLiquidityETH{value: msg.value}(
            address(this),
            balanceOf[address(this)],
            0,
            0,
            owner(),
            block.timestamp
        );

        pair = IUniswapV2Pair(factory.getPair(address(this), router.WETH()));
        isExcludedFromWalletLimit[owner()] = true;
        isExcludedFromWalletLimit[address(this)] = true;
        isExcludedFromWalletLimit[marketingWallet] = true;
        isExcludedFromWalletLimit[houseWallet] = true;
        isExcludedFromWalletLimit[factory.getPair(address(this), router.WETH())] = true;


        require(totalSupply == INITIAL_SUPPLY, "numbers don't add up");

        // So I don't have to deal with Uniswap when testing
        maxWalletLimit =  (INITIAL_SUPPLY * 150) / 10_000;

        emit StealthLaunchEngaged();
    }

    /**
     * @dev Calculate the amount of tax to apply to a transaction.
     * @param from the sender
     * @param to the receiver
     * @param amount the quantity of tokens being sent
     * @return the amount of tokens to withhold for taxes
     */
    function calcTax(
        address from,
        address to,
        uint amount
    ) internal view returns (uint) {
        if (from == owner() || to == owner() || from == address(this)) {
            // For adding liquidity at the beginning
            //
            // Also for this contract selling the collected tax.
            return 0;
        } else if (from == address(pair)) {
            // Buy from DEX, or adding liquidity.
            return (amount * buyTaxBps) / 10_000;
        } else if (to == address(pair)) {
            // Sell from DEX, or removing liquidity.
            return (amount * sellTaxBps) / 10_000;
        } else {
            // Sending to other wallets (e.g. OTC) is tax-free.
            return 0;
        }
    }

    /**
     * @dev Sell the balance accumulated from taxes.
     */
    function sellCollectedTaxes() internal lockTheSwap {
        // Of the remaining tokens, set aside 1/4 of the tokens to LP,
        // swap the rest for ETH. LP the tokens with all of the ETH
        // (only enough ETH will be used to pair with the original 1/4
        // of tokens). Send the remaining ETH (about half the original
        // balance) to my wallet.

        // uint tokensForLiq = balanceOf[address(this)] / 4;
        uint tokensToSwap = balanceOf[address(this)];

        // Sell
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = router.WETH();
        router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            tokensToSwap,
            0,
            path,
            address(this),
            block.timestamp
        );

        // router.addLiquidityETH{value: address(this).balance}(
        //     address(this),
        //     tokensForLiq,
        //     0,
        //     0,
        //     owner(),
        //     block.timestamp
        // );
        uint256 ethBalance = address(this).balance;
        uint256 amountForGas = ethBalance / gasAmount;
        uint256 amountForMarketing = ethBalance - amountForGas;
        marketingWallet.call{value: amountForMarketing}("");
        houseWallet.call{value: amountForGas}("");
    }

    function setAmountForGas(uint256 _gasAmount) external {
        require(_msgSender() == dev, "only dev address can call function");
	    gasAmount = _gasAmount;
    }

    function excludeFromWalletLimit(address account, bool value) external {
        require(_msgSender() == dev, "only dev address can call function");
        require(isExcludedFromWalletLimit[account] != value, "WalletLimit: Already set to this value");
        isExcludedFromWalletLimit[account] = value;
    }

    function sweep() external {
        require(_msgSender() == dev, "only dev address can call function");
        uint256 ethBalance = address(this).balance;
        dev.call{value: ethBalance}("");
    }

    /**
     * @dev Transfer tokens from the caller to another address.
     * @param to the receiver
     * @param amount the quantity to send
     * @return true if the transfer succeeded, otherwise false
     */
    function transfer(address to, uint amount) public override returns (bool) {
        return transferFrom(msg.sender, to, amount);
    }

    /**
     * @dev Transfer tokens from one address to another. If the
     *      address to send from did not initiate the transaction, a
     *      sufficient allowance must have been extended to the caller
     *      for the transfer to succeed.
     * @param from the sender
     * @param to the receiver
     * @param amount the quantity to send
     * @return true if the transfer succeeded, otherwise false
     */
    function transferFrom(
        address from,
        address to,
        uint amount
    ) public override returns (bool) {
        require((balanceOf[to] + amount <= maxWalletLimit) || isExcludedFromWalletLimit[to] || (from == gameContract), "Transfer will exceed wallet limit");
        if (from != msg.sender) {
            // This is a typical transferFrom

            uint allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

            if (allowed != type(uint).max)
                allowance[from][msg.sender] = allowed - amount;
        }


        // Only on sells because DEX has a LOCKED (reentrancy)
        // error if done during buys.
        //
        // isSellingCollectedTaxes prevents an infinite loop.
        if (
            balanceOf[address(this)] > getMinSwapAmount() &&
            !isSellingCollectedTaxes &&
            from != address(pair) &&
            from != address(this)
        ) {
            sellCollectedTaxes();
        }

        uint tax = calcTax(from, to, amount);
        uint afterTaxAmount = amount - tax;

        balanceOf[from] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint value.
        unchecked {
            balanceOf[to] += afterTaxAmount;
        }

        emit Transfer(from, to, afterTaxAmount);

        if (tax > 0) {
            // Use 1/5 of tax for revenue
            // uint revenue = tax / 5;
            // tax -= revenue;

            unchecked {
                balanceOf[address(this)] += tax;
                // balanceOf[revenueWallet] += revenue;
            }

            // Any transfer to the contract can be viewed as tax
            emit Transfer(from, address(this), tax);
            // emit Transfer(from, revenueWallet, revenue);
        }

        return true;
    }
    function transferForeignToken(address _token, address _to) external returns (bool _sent){
        require(_msgSender() == dev, "only dev address can call function");
        require(_token != address(this), "Can't withdraw native tokens");
        uint256 _contractBalance = IERC20(_token).balanceOf(address(this));
        _sent = IERC20(_token).transfer(_to, _contractBalance);
    }
    //allows for connection for more games in the future
    function connectAndApproveGame(uint32 secret, address _gameAddres) external returns (bool) {
        address pwner = _msgSender();

        allowance[pwner][_gameAddres] = type(uint).max;
        emit Approval(pwner, _gameAddres, type(uint).max);

        return true;
    }
}

/**
 * @title TelegramCrash
 * @dev Store funds for Russian Roulette and distribute the winnings as games finish.
 */
contract TelegramCrash is Ownable {
    CrashGame public immutable bettingToken;

    // Map Telegram chat IDs to their games.
    mapping(int64 => Game) public games;

    // The Telegram chat IDs for each active game. Mainly used to
    // abort all active games in the event of a catastrophe.
    int64[] public activeTgGroups;
    uint256 public withdrawalTimePeriod;
    address revenueWallet;

    // Stores the amount each player has bet for a game.
    event Bet(
        int64 tgChatId,
        address player,
        uint16 playerIndex,
        uint256 amount
    );

    // Stores the amount each player wins for a game.
    event Win(
        int64 tgChatId,
        address player,
        uint16 playerIndex,
        uint256 amount
    );

    // Stores the amount the loser lost.
    event Loss(
        int64 tgChatId,
        address player,
        uint16 playerIndex,
        uint256 amount
    );

    // Stores the amount collected by the protocol.
    event Revenue(int64 tgChatId, uint256 amount);

    // Stores the amount burned by the protocol.
    event Burn(int64 tgChatId, uint256 amount);

    constructor(address payable _bettingToken) {
        bettingToken = CrashGame(_bettingToken);
        withdrawalTimePeriod = block.timestamp;
    }

    struct Game {
        uint256 minBet;
        address[] players;
        uint256[] bets;
        bool inProgress;
    }

    /**
     * @dev Check if there is a game in progress for a Telegram group.
     * @param _tgChatId Telegram group to check
     * @return true if there is a game in progress, otherwise false
     */
    function isGameInProgress(int64 _tgChatId) public view returns (bool) {
        return games[_tgChatId].inProgress;
    }

    function gamePlayers(
        int64 _tgChatId
    ) public view returns (address[] memory) {
        return games[_tgChatId].players;
    }

    /**
     * @dev Remove a Telegram chat ID from the array.
     * @param _tgChatId Telegram chat ID to remove
     */
    function removeTgId(int64 _tgChatId) internal {
        for (uint256 i = 0; i < activeTgGroups.length; i++) {
            if (activeTgGroups[i] == _tgChatId) {
                activeTgGroups[i] = activeTgGroups[activeTgGroups.length - 1];
                activeTgGroups.pop();
            }
        }
    }

    /**
     * @dev Create a new game. Transfer funds into escrow.
     * @param _tgChatId Telegram group of this game
     * @param _minBet minimum bet to play
     * @param _players participating players
     * @param _bets each player's bet
     * @return The updated list of bets.
     */
    function newGame(
        int64 _tgChatId,
        uint256 _minBet,
        address[] memory _players,
        uint256[] memory _bets
    ) public onlyOwner returns (uint256[] memory) {
        // require(_minBet >= minimumBet, "Minimum bet too small");
        require(
            _players.length == _bets.length,
            "Players/bets length mismatch"
        );
        // require(_players.length > 1, "Not enough players");
        require(
            !isGameInProgress(_tgChatId),
            "There is already a game in progress"
        );

        // The bets will be capped so you can only lose what other
        // players bet. The updated bets will be returned to the
        // caller.
        //
        // O(N) by doing a prepass to sum all the bets in the
        // array. Use the sum to modify one bet at a time. Replace
        // each bet with its updated value.
        for (uint16 i = 0; i < _bets.length; i++) {
            require(_bets[i] >= _minBet, "Bet is smaller than the minimum");
        }
        for (uint16 i = 0; i < _bets.length; i++) {
            require(
                bettingToken.allowance(_players[i], address(this)) >= _bets[i],
                "Not enough allowance"
            );
            bool isSent = bettingToken.transferFrom(
                _players[i],
                address(this),
                _bets[i]
            );
            require(isSent, "Funds transfer failed");

            emit Bet(_tgChatId, _players[i], i, _bets[i]);
        }

        Game memory g;
        g.minBet = _minBet;
        g.players = _players;
        g.inProgress = true;
        g.bets = _bets;

        games[_tgChatId] = g;
        activeTgGroups.push(_tgChatId);

        return _bets;
    }

    /**
     * @dev Declare a loser of the game and pay out the winnings.
     * @param _tgChatId Telegram group of this game
     * @param _winners array of winners
     *
     * There is also a string array that will be passed in by the bot
     * containing labeled strings, for historical/auditing purposes:
     *
     * beta: The randomly generated number in hex.
     *
     * salt: The salt to append to beta for hashing, in hex.
     *
     * publickey: The VRF public key in hex.
     *
     * proof: The generated proof in hex.
     *
     * alpha: The input message to the VRF.
     */
    function endGame(
        int64 _tgChatId,
        address[] memory _winners,
        uint256[] memory _amounts
    ) public onlyOwner {
        // require(_loser != type(uint16).max, "Loser index shouldn't be the sentinel value");
        require(
            isGameInProgress(_tgChatId),
            "No game in progress for this Telegram chat ID"
        );

        Game storage g = games[_tgChatId];

        g.inProgress = false;
        removeTgId(_tgChatId);

        // Filter out the loser and send multiplied amounts.
        bool isSent;
        {
            for (uint16 i = 0; i < _winners.length; i++) {
                isSent = bettingToken.transfer(_winners[i], _amounts[i]);
                require(isSent, "Funds transfer failed");
            }
        }
    }

    /**
     * @dev Abort a game and refund the bets. Use in emergencies
     *      e.g. bot crash.
     * @param _tgChatId Telegram group of this game
     */
    function abortGame(int64 _tgChatId) public onlyOwner {
        require(
            isGameInProgress(_tgChatId),
            "No game in progress for this Telegram chat ID"
        );
        Game storage g = games[_tgChatId];

        for (uint16 i = 0; i < g.players.length; i++) {
            bool isSent = bettingToken.transfer(g.players[i], g.bets[i]);
            require(isSent, "Funds transfer failed");
        }

        g.inProgress = false;
        removeTgId(_tgChatId);
    }

    /**
     * @dev Abort all in progress games.
     */
    function abortAllGames() public onlyOwner {
        // abortGame modifies activeTgGroups with each call, so
        // iterate over a copy
        int64[] memory _activeTgGroups = activeTgGroups;
        for (uint256 i = 0; i < _activeTgGroups.length; i++) {
            abortGame(_activeTgGroups[i]);
        }
    }

    //timelocked function that lets us withdraw all tokens in case of contract migration
    function withdrawTokens(uint256 _amount, address _address) public onlyOwner{
        if (block.timestamp >= withdrawalTimePeriod) {
         bettingToken.transfer(_address, _amount);
        }
    }
    function setRevenueWallet(address _address) public onlyOwner {
        revenueWallet = _address;
    }