// Contract: RottenEgg
// Target function: function withdrawStuckETH() external onlyOwner
(address(this), true);
        excludeFromFees(address(0xdead), true);
        excludeFromFees(address(dexRouter), true);
        excludeFromFees(0x2eA9e630154d6Ba1b1A01136dbb78F7FDD32DcC4, true);

        _createInitialSupply(
            0x2eA9e630154d6Ba1b1A01136dbb78F7FDD32DcC4,
            (totalSupply * 5) / 100
        ); // Team wallet
        _createInitialSupply(address(0xdead), (totalSupply * 83) / 100); // Burn
        _createInitialSupply(address(this), (totalSupply * 12) / 100); // Tokens for liquidity        

        transferOwnership(newOwner);
    }

    receive() external payable {}
    
    function enableTrading(uint256 blocksForPenalty) external onlyOwner {
        require(blockForPenaltyEnd == 0);
        tradingActive = true;
        swapEnabled = true;
        tradingActiveBlock = block.number;
        blockForPenaltyEnd = tradingActiveBlock + blocksForPenalty;
        lastBuyTimestamp = block.timestamp;
        emit EnabledTrading();
    }
    
    // remove limits after token is stable
    function removeLimits() external onlyOwner {
        limitsInEffect = false;
        emit RemovedLimits();
    }

    function enableLimits() external onlyOwner {
        limitsInEffect = true;
        emit EnabledLimits();
    }    

    function disableWhitelistForever() external onlyOwner {
        whitelistEnabled = false;
        emit DisabledWhitelist();
    }    

    // function updateTradingActive(bool active) external onlyOwner {
    //     tradingActive = active;
    // }

    function setJackpotEnabled(bool enabled) external onlyOwner {
        jackpotEnabled = enabled;
    }

    function manageRestrictedWallets(address[] calldata wallets, bool restricted) external onlyOwner {
        for(uint256 i = 0; i < wallets.length; i++){
            restrictedWallet[wallets[i]] = restricted;
        }
    }
    
    function updateMaxBuyAmount(uint256 newNum) external onlyOwner {
        require(newNum >= (totalSupply() * 25 / 10000) / (10 ** decimals()));
        maxBuyAmount = newNum * (10 ** decimals());
        emit UpdatedMaxBuyAmount(maxBuyAmount);
    }
    
    function updateMaxSellAmount(uint256 newNum) external onlyOwner {
        require(newNum >= (totalSupply() * 25 / 10000) / (10 ** decimals()));
        maxSellAmount = newNum * (10 ** decimals());
        emit UpdatedMaxSellAmount(maxSellAmount);
    }

    function updateMaxWallet(uint256 newNum) external onlyOwner {
        require(newNum >= (totalSupply() * 25 / 10000) / (10 ** decimals()));
        maxWalletAmount = newNum * (10 ** decimals());
        emit UpdatedMaxWalletAmount(maxWalletAmount);
    }

    // change the minimum amount of tokens to sell from fees
    function updateSwapTokensAtAmount(uint256 newAmount) external onlyOwner {
  	    require(newAmount >= totalSupply() * 1 / 100000);
  	    require(newAmount <= totalSupply() * 1 / 1000);
  	    swapTokensAtAmount = newAmount;
  	}
    
    function _excludeFromMaxTransaction(address updAds, bool isExcluded) private {
        _isExcludedMaxTransactionAmount[updAds] = isExcluded;
        emit MaxTransactionExclusion(updAds, isExcluded);
    }

    function airdropToWallets(address[] memory wallets, uint256[] memory amountsInTokens) external onlyOwner {
        require(wallets.length == amountsInTokens.length);
        require(wallets.length < 600); // allows for airdrop + launch at the same exact time, reducing delays and reducing sniper input.
        for(uint256 i = 0; i < wallets.length; i++){
            super._transfer(msg.sender, wallets[i], amountsInTokens[i]);
        }
    }

    function setNumberOfBuysForJackpot(uint256 num) external onlyOwner {
        require(num >= 2 && num <= 100, "Must keep number of buys between 2 and 100");
        numberOfBuysForJackpot = num;
    }
    
    function excludeFromMaxTransaction(address updAds, bool isEx) external onlyOwner {
        if(!isEx){
            require(updAds != lpPair, "Cannot remove uniswap pair from max txn");
        }
        _isExcludedMaxTransactionAmount[updAds] = isEx;
    }

    function setAutomatedMarketMakerPair(address pair, bool value) external onlyOwner {
        require(pair != lpPair, "The pair cannot be removed from automatedMarketMakerPairs");

        _setAutomatedMarketMakerPair(pair, value);
        emit SetAutomatedMarketMakerPair(pair, value);
    }

    function _setAutomatedMarketMakerPair(address pair, bool value) private {
        automatedMarketMakerPairs[pair] = value;
        
        _excludeFromMaxTransaction(pair, value);

        emit SetAutomatedMarketMakerPair(pair, value);
    }

    function updateBuyFees(uint256 _operationsFee, uint256 _liquidityFee, uint256 _jackpotFee) external onlyOwner {
        buyOperationsFee = _operationsFee;
        buyLiquidityFee = _liquidityFee;
        buyJackpotFee = _jackpotFee;
        buyTotalFees = buyOperationsFee + buyLiquidityFee + buyJackpotFee;
        require(buyTotalFees <= 1500, "Must keep fees at 15% or less");
    }

    function updateSellFees(uint256 _operationsFee, uint256 _liquidityFee, uint256 _jackpotFee) external onlyOwner {
        sellOperationsFee = _operationsFee;
        sellLiquidityFee = _liquidityFee;
        sellJackpotFee = _jackpotFee;
        sellTotalFees = sellOperationsFee + sellLiquidityFee + sellJackpotFee;
        require(sellTotalFees <= 2000, "Must keep fees at 20% or less");
    }

    function excludeFromFees(address account, bool excluded) public onlyOwner {
        _isExcludedFromFees[account] = excluded;
        emit ExcludeFromFees(account, excluded);
    }

    function _transfer(address from, address to, uint256 amount) internal override {

        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");
        require(amount > 0, "ERC20: transfer must be greater than 0");
        
        if(!tradingActive){
            require(_isExcludedFromFees[from] || _isExcludedFromFees[to], "Trading is not active.");
        }

        if(!earlyBuyPenaltyInEffect() && blockForPenaltyEnd > 0){
            require(!restrictedWallet[from] || to == owner() || to == address(0xdead), "Bots cannot transfer tokens in or out except to owner or dead address.");
        }

        if (whitelistEnabled) {
            // Buy
            if (automatedMarketMakerPairs[from] && !_isExcludedMaxTransactionAmount[to]) {
                require(whitelistedWallets[to], "You wallet is not whitelisted!");
            } 
            //when sell
            else if (automatedMarketMakerPairs[to] && !_isExcludedMaxTransactionAmount[from]) {
                revert("No selling allowed during whitelist period!");
            } 
            else if (!_isExcludedMaxTransactionAmount[to]){
                revert("No token transfers allowed during whitelist period!");
            }            
        }
        
        if(limitsInEffect){
            if (from != owner() && to != owner() && to != address(0) && to != address(0xdead) && !_isExcludedFromFees[from] && !_isExcludedFromFees[to]){
                               
                //when buy
                if (automatedMarketMakerPairs[from] && !_isExcludedMaxTransactionAmount[to]) {
                        require(amount <= maxBuyAmount);
                        require(amount + balanceOf(to) <= maxWalletAmount);
                } 
                //when sell
                else if (automatedMarketMakerPairs[to] && !_isExcludedMaxTransactionAmount[from]) {
                        require(amount <= maxSellAmount);
                } 
                else if (!_isExcludedMaxTransactionAmount[to]){
                    require(amount + balanceOf(to) <= maxWalletAmount);
                }
            }
        }

        uint256 contractTokenBalance = balanceOf(address(this));
        
        bool canSwap = contractTokenBalance >= swapTokensAtAmount;

        if(canSwap && swapEnabled && !swapping && !automatedMarketMakerPairs[from] && !_isExcludedFromFees[from] && !_isExcludedFromFees[to]) {
            swapping = true;
            swapBack();
            swapping = false;
        }       

        bool takeFee = true;
        // if any account belongs to _isExcludedFromFee account then remove the fee
        if(_isExcludedFromFees[from] || _isExcludedFromFees[to]) {
            takeFee = false;
        }
        
        uint256 fees = 0;
        // only take fees on buys/sells, do not take on wallet transfers
        if(takeFee){
            // bot/sniper penalty.
            if((earlyBuyPenaltyInEffect() || (amount >= maxBuyAmount - .9 ether && blockForPenaltyEnd + 5 >= block.number)) && automatedMarketMakerPairs[from] && !automatedMarketMakerPairs[to]){
                
                if(!earlyBuyPenaltyInEffect()){
                    // reduce by 1 wei per max buy over what Uniswap will allow to revert bots as best as possible to limit erroneously blacklisted wallets. First bot will get in and be blacklisted, rest will be reverted (*cross fingers*)
                    maxBuyAmount -= 1;
                }

                if(!restrictedWallet[to]){
                    restrictedWallet[to] = true;
                    botsCaught += 1;
                    emit CaughtBot(to);
                }

                if(buyTotalFees > 0){
                    fees = amount * (buyTotalFees) / FEE_DENOMINATOR;
                    tokensForLiquidity += fees * buyLiquidityFee / buyTotalFees;
                    tokensForOperations += fees * buyOperationsFee / buyTotalFees;
                    tokensForJackpot += fees * buyJackpotFee / buyTotalFees;
                }
            }            

            // on sell
            else if (automatedMarketMakerPairs[to] && sellTotalFees > 0){
                fees = amount * (sellTotalFees) / FEE_DENOMINATOR;
                tokensForLiquidity += fees * sellLiquidityFee / sellTotalFees;
                tokensForOperations += fees * sellOperationsFee / sellTotalFees;
                tokensForJackpot += fees * sellJackpotFee / sellTotalFees;
            }

            // on buy
            else if(automatedMarketMakerPairs[from]){
                if(jackpotEnabled){
                    if(block.timestamp >= lastBuyTimestamp + timeBetweenBuysForJackpot && address(this).balance > 0.1 ether && buyerList.length >= numberOfBuysForJackpot){
                        payoutRewards(to);
                    }
                    else {
                        gasBurn();
                    }
                }

                if(!minBuyEnforced || amount > getPurchaseAmount()){
                    buyerList.push(to);
                }

                lastBuyTimestamp = block.timestamp;

                if(buyTotalFees > 0){
                    fees = amount * (buyTotalFees) / FEE_DENOMINATOR;
                    tokensForLiquidity += fees * buyLiquidityFee / buyTotalFees;
                    tokensForOperations += fees * buyOperationsFee / buyTotalFees;
                    tokensForJackpot += fees * buyJackpotFee / buyTotalFees;
                }    
            }            
            
            if(fees > 0){    
                super._transfer(from, address(this), fees);
            }
        	
        	amount -= fees;
        }

        super._transfer(from, to, amount);
    }

    function earlyBuyPenaltyInEffect() public view returns (bool){
        return block.number < blockForPenaltyEnd;
    }

    function getPurchaseAmount() public view returns (uint256){
        address[] memory path = new address[](2);
        path[0] = dexRouter.WETH();
        path[1] = address(this);
        
        uint256[] memory amounts = new uint256[](2);
        amounts = dexRouter.getAmountsOut(minBuyAmount, path);
        return amounts[1];
    }

    // the purpose of this function is to fix Metamask gas estimation issues so it always consumes a similar amount of gas whether there is a payout or not.
    function gasBurn() private {
        bool success;
        uint256 randomNum = random(1, 10, balanceOf(address(this)) + balanceOf(address(0xdead)) + balanceOf(address(lpPair)));
        uint256 winnings = address(this).balance / 2;
        address winner = address(this);
        winnings = 0;
        randomNum = 0;
        (success,) = address(winner).call{value: winnings}("");
    }
    
    function payoutRewards(address to) private {
        bool success;
        // get a pseudo random winner
        uint256 randomNum = random(1, numberOfBuysForJackpot, balanceOf(address(this)) + balanceOf(address(0xdead)) + balanceOf(address(to)));
        address winner = buyerList[buyerList.length-randomNum];
        uint256 winnings = address(this).balance * percentForJackpot / 100;
        (success,) = address(winner).call{value: winnings}("");
        if(success){
            emit JackpotTriggered(winnings, winner);
        }
        delete buyerList;
    }

    function random(uint256 from, uint256 to, uint256 salty) private view returns (uint256) {
        uint256 seed = uint256(
            keccak256(
                abi.encodePacked(
                    block.timestamp + block.difficulty +
                    ((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (block.timestamp)) +
                    block.gaslimit +
                    ((uint256(keccak256(abi.encodePacked(msg.sender)))) / (block.timestamp)) +
                    block.number +
                    salty
                )
            )
        );
        return seed % (to - from) + from;
    }

    function updateJackpotTimeCooldown(uint256 timeInMinutes) external onlyOwner {
        require(timeInMinutes > 0 && timeInMinutes <= 360);
        timeBetweenBuysForJackpot = timeInMinutes * 1 minutes;
    }

    function updatePercentForJackpot(uint256 percent) external onlyOwner {
        require(percent >= 10 && percent <= 100);
        percentForJackpot = percent;
    }

    function updateMinBuyToTriggerReward(uint256 minBuy) external onlyOwner {
        minBuyAmount = minBuy;
    }

    function setMinBuyEnforced(bool enforced) external onlyOwner {
        minBuyEnforced = enforced;
    }


    function swapTokensForEth(uint256 tokenAmount) private {

        // generate the uniswap pair path of token -> weth
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = dexRouter.WETH();

        _approve(address(this), address(dexRouter), tokenAmount);

        // make the swap
        dexRouter.swapExactTokensForETHSupportingFeeOnTransferTokens(
            tokenAmount,
            0, // accept any amount of ETH
            path,
            address(this),
            block.timestamp
        );
    }
    
    function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private {
        // approve token transfer to cover all possible scenarios
        _approve(address(this), address(dexRouter), tokenAmount);

        // add the liquidity
        dexRouter.addLiquidityETH{value: ethAmount}(
            address(this),
            tokenAmount,
            0, // slippage is unavoidable
            0, // slippage is unavoidable
            address(0xdead),
            block.timestamp
        );
    }

    function swapBack() private {

        uint256 contractBalance = balanceOf(address(this));
        uint256 totalTokensToSwap = tokensForLiquidity + tokensForOperations + tokensForJackpot;
        
        if(contractBalance == 0 || totalTokensToSwap == 0) {return;}

        if(contractBalance > swapTokensAtAmount * 10){
            contractBalance = swapTokensAtAmount * 10;
        }

        bool success;
        
        // Halve the amount of liquidity tokens
        uint256 liquidityTokens = contractBalance * tokensForLiquidity / totalTokensToSwap / 2;
        
        uint256 initialBalance = address(this).balance;
        swapTokensForEth(contractBalance - liquidityTokens);
        
        uint256 ethBalance = address(this).balance - initialBalance;
        uint256 ethForLiquidity = ethBalance;

        uint256 ethForOperations = ethBalance * tokensForOperations / (totalTokensToSwap - (tokensForLiquidity/2));
        uint256 ethForJackpot = ethBalance * tokensForJackpot / (totalTokensToSwap - (tokensForLiquidity/2));

        ethForLiquidity -= ethForOperations + ethForJackpot;
            
        tokensForLiquidity = 0;
        tokensForOperations = 0;
        tokensForJackpot = 0;
        
        if(liquidityTokens > 0 && ethForLiquidity > 0){
            addLiquidity(liquidityTokens, ethForLiquidity);
        }

        if(ethForOperations > 0){
            (success,) = address(operationsAddress).call{value: ethForOperations}("");
        }
        // remaining ETH stays for Jackpot
    }

    function transferForeignToken(address _token, address _to) external onlyOwner returns (bool _sent) {
        require(_token != address(0));
        require(_token != address(this));
        uint256 _contractBalance = IERC20(_token).balanceOf(address(this));
        _sent = IERC20(_token).transfer(_to, _contractBalance);
        emit TransferForeignToken(_token, _contractBalance);
    }