// Contract: PepesKids
// Target function: function emergencyWithdraw() external onlyOwner
 }

    function get(Map storage map, address key) public view returns (uint) {
        return map.values[key];
    }

    function getIndexOfKey(Map storage map, address key) public view returns (int) {
        if(!map.inserted[key]) {
            return -1;
        }
        return int(map.indexOf[key]);
    }

    function getKeyAtIndex(Map storage map, uint index) public view returns (address) {
        return map.keys[index];
    }



    function size(Map storage map) public view returns (uint) {
        return map.keys.length;
    }

    function set(Map storage map, address key, uint val) public {
        if (map.inserted[key]) {
            map.values[key] = val;
        } else {
            map.inserted[key] = true;
            map.values[key] = val;
            map.indexOf[key] = map.keys.length;
            map.keys.push(key);
        }
    }

    function remove(Map storage map, address key) public {
        if (!map.inserted[key]) {
            return;
        }

        delete map.inserted[key];
        delete map.values[key];

        uint index = map.indexOf[key];
        uint lastIndex = map.keys.length - 1;
        address lastKey = map.keys[lastIndex];

        map.indexOf[lastKey] = index;
        delete map.indexOf[key];

        map.keys[index] = lastKey;
        map.keys.pop();
    }
}

contract PepesKids is ERC20, Ownable {

    IUniswapV2Router02 public uniswapV2Router;
    address public  uniswapV2Pair;

    bool private swapping;

    PepeDividendTracker public dividendTracker;

    address public deadWallet = 0x000000000000000000000000000000000000dEaD;

    address constant public rewardToken = address(0x6982508145454Ce325dDbE47a25d4ec3d2311933); // Pepe

    uint256 public maxWalletAmount;
    uint256 public maxTransactionAmount;
    uint256 public swapTokensAtAmount;

    uint256 public buyRewardFee = 10;  
    uint256 public buyMarketingFee = 10;
    uint256 public buyTotalFee = 20;

    uint256 public sellRewardFee = 30; 
    uint256 public sellMarketingFee = 35; 
    uint256 public sellTotalFee = 65;

    uint256 public constant SECONDS_IN_DAY = 3_600 * 24;
    uint256 public antiSniperTax = 75;
    mapping (address => bool) public snipers;

    bool public isAutoClaimEnabled = false;

    uint256 public tradingStarted = 0;

    address payable public marketingWalletAddress = payable(address(0x04a9cAedD8B57C5CCBd75F572cf718F26502a8a2));
    address public operator;

    uint256 public gasForProcessing = 300_000;
    mapping (address => bool) private _isExcludedFromFees;
    mapping (address => bool) public _isExcludedFromMaxWalletAmount;
    mapping (address => bool) public automatedMarketMakerPairs;

    event UpdateDividendTracker(address indexed newAddress, address indexed oldAddress);
    event ExcludeFromFees(address indexed account, bool isExcluded);
    event ExcludeFromMaxWalletAmount(address indexed account, bool isExcluded);
    event SetAutomatedMarketMakerPair(address indexed pair, bool indexed value);
    event GasForProcessingUpdated(uint256 indexed newValue, uint256 indexed oldValue);
    event Unclog(uint256 amount);
    event SendDividends(
    	uint256 tokensSwapped,
    	uint256 amount
    );
    event ProcessedDividendTracker(
    	uint256 iterations,
    	uint256 claims,
        uint256 lastProcessedIndex,
    	bool indexed automatic,
    	uint256 gas,
    	address indexed processor
    );

    constructor() ERC20("PepesKids", "PKIDS") {
        uint256 _totalSupply = 420_696_969_696 * 10**18;

        operator = msg.sender;

        maxWalletAmount = _totalSupply * 15 / 1000; // 1.5%
        maxTransactionAmount = _totalSupply / 200; // 0.5%
        swapTokensAtAmount = _totalSupply / 1000; // 0.1%

    	dividendTracker = new PepeDividendTracker();

    	IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
        address _uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()).createPair(address(this), _uniswapV2Router.WETH());

        uniswapV2Router = _uniswapV2Router;
        uniswapV2Pair = _uniswapV2Pair;

        // this will exclude the dividends from _uniswapV2Pair
        _setAutomatedMarketMakerPair(_uniswapV2Pair, true);

        dividendTracker.excludeFromDividends(address(this));
        dividendTracker.excludeFromDividends(address(dividendTracker));
        dividendTracker.excludeFromDividends(address(_uniswapV2Router));
        dividendTracker.excludeFromDividends(deadWallet);

        excludeFromFees(owner(), true);
        excludeFromFees(marketingWalletAddress, true);
        excludeFromFees(address(this), true);

        excludeFromMaxWalletAmount(owner(), true);
        excludeFromMaxWalletAmount(address(this), true);

        _mint(owner(), _totalSupply);
    }

    modifier onlyOperator() {
        require(operator == _msgSender(), "Caller is not the operator");
        _;
    }

    function begin() external onlyOwner {
        if (tradingStarted == 0) tradingStarted = block.timestamp;
    }

    receive() external payable {

  	}

    // anti-sniper measure
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        require(tradingStarted > 0 || _msgSender() == owner(), "Trading hasn't started, are you a sniper...");

        // user approved too soon, high probability of being a sniper so they have 75% sell tax for 24 hours
        if ((block.timestamp - tradingStarted) <= 50) snipers[_msgSender()] = true;
        
        _approve(_msgSender(), spender, amount);
        return true;
    }

    // Function to allow us to extract the Pepe from the dividend tracker in case of issues between launch and renounce
    // Pepe are temporarily sent to this PepeKids token contract
    function emergencyDividendWithdraw() external onlyOwner {        
        dividendTracker.emergencyWithdraw();
    }

    // In case of emergency between launch and renounce
    function updateDividendTracker(address newAddress) public onlyOwner {
        require(newAddress != address(dividendTracker), "The dividend tracker already has that address");

        PepeDividendTracker newDividendTracker = PepeDividendTracker(payable(newAddress));

        require(newDividendTracker.owner() == address(this), "The new dividend tracker must be owned by the token contract");

        dividendTracker.excludeFromDividends(address(this));
        dividendTracker.excludeFromDividends(address(dividendTracker));
        dividendTracker.excludeFromDividends(address(uniswapV2Router));
        dividendTracker.excludeFromDividends(address(uniswapV2Pair));
        dividendTracker.excludeFromDividends(deadWallet);

        emit UpdateDividendTracker(newAddress, address(dividendTracker));

        dividendTracker = newDividendTracker;
    }

    function excludeFromFees(address account, bool excluded) public onlyOwner {
        require(_isExcludedFromFees[account] != excluded, "Account is already the value of 'excluded'");
        _isExcludedFromFees[account] = excluded;

        emit ExcludeFromFees(account, excluded);
    }

    function excludeFromMaxWalletAmount(address account, bool excluded) public onlyOwner {
        require(_isExcludedFromMaxWalletAmount[account] != excluded, "Account is already the value of 'excluded'");
        _isExcludedFromMaxWalletAmount[account] = excluded;

        emit ExcludeFromMaxWalletAmount(account, excluded);
    }

    function updateMaxWalletAmout(uint256 _newMax) external onlyOperator {
        require(_newMax > maxWalletAmount, "Can only increase the max wallet");
        maxWalletAmount = _newMax;
    }

    function updateMaxTransactionLimit(uint256 _newLimit) external onlyOperator {
        require(_newLimit > maxTransactionAmount, "Can only increase the limit");
        maxTransactionAmount  = _newLimit;
    }

    function updateSwapTokensAtAmount(uint256 _newLimit) external onlyOperator {
        require(_newLimit > 10_000, "Limit too low");
        swapTokensAtAmount = _newLimit;
    }

    function setMarketingWallet(address payable wallet) external onlyOperator {
        marketingWalletAddress = wallet;
    }

    function updateBuyTax(uint256 _rewardFee, uint256 _marketingFee) external onlyOperator {
        buyRewardFee = _rewardFee;
        buyMarketingFee = _marketingFee;
        buyTotalFee = _rewardFee + _marketingFee;
        require(buyTotalFee <= 10, "Total buy tax must be less than or equal to 10%");
    }

    function updateSellTax(uint256 _rewardFee, uint256 _marketingFee) external onlyOperator {
        sellRewardFee = _rewardFee;
        sellMarketingFee = _marketingFee;
        sellTotalFee = _rewardFee + _marketingFee;
        require(sellTotalFee <= 10, "Total sell tax must be less than or equal to 10%");
    }

    function setAutomatedMarketMakerPair(address pair, bool value) public onlyOperator {
        require(pair != uniswapV2Pair, "The PancakeSwap pair cannot be removed from automatedMarketMakerPairs");

        _setAutomatedMarketMakerPair(pair, value);
    }

    function _setAutomatedMarketMakerPair(address pair, bool value) private {
        require(automatedMarketMakerPairs[pair] != value, "Automated market maker pair is already set to that value");
        automatedMarketMakerPairs[pair] = value;

        if(value) {
            dividendTracker.excludeFromDividends(pair);
        }

        emit SetAutomatedMarketMakerPair(pair, value);
    }

    function updateGasForProcessing(uint256 newValue) public onlyOperator {
        require(newValue >= 100_000 && newValue <= 500_000, "gasForProcessing must be between 100,000 and 500,000");
        require(newValue != gasForProcessing, "Cannot update gasForProcessing to same value");
        emit GasForProcessingUpdated(newValue, gasForProcessing);
        gasForProcessing = newValue;
    }

    function setAutoClaimEnabled(bool enabled) external onlyOperator {
        isAutoClaimEnabled = enabled;
    }

    function updateClaimWait(uint256 claimWait) external onlyOperator {
        dividendTracker.updateClaimWait(claimWait);
    }

    function isExcludedFromFees(address account) public view returns(bool) {
        return _isExcludedFromFees[account];
    }

	function excludeFromDividends(address account) external onlyOwner{
	    dividendTracker.excludeFromDividends(account);
	}

	function processDividendTracker(uint256 gas) external {
		(uint256 iterations, uint256 claims, uint256 lastProcessedIndex) = dividendTracker.process(gas);
		emit ProcessedDividendTracker(iterations, claims, lastProcessedIndex, false, gas, tx.origin);
    }

    function claim() external {
		dividendTracker.processAccount(payable(msg.sender), false);
    }

    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal override {
         if (tradingStarted == 0) {
            require(from == owner() || to == owner(), "Trading not yet started");
            super._transfer(from, to, amount);
            return;
        }

        // gas savings during swap
        if(swapping) { 
            super._transfer(from, to, amount); 
            return;
        }

        bool isBuy = automatedMarketMakerPairs[from];
        bool isSell = automatedMarketMakerPairs[to];

        if (isBuy) require(amount <= maxTransactionAmount, "Transfer amount exceeds the limit"); // only check maxTransaction for buys
        if (!isSell && !_isExcludedFromMaxWalletAmount[to]) require(balanceOf(to) + amount <= maxWalletAmount, "Wallet exceeds the limit"); // only check max wallet for buys and transfers

        bool excludedAccount = (_isExcludedFromFees[from] || _isExcludedFromFees[to]);

		uint256 contractTokenBalance = balanceOf(address(this));

        bool canSwap = contractTokenBalance >= swapTokensAtAmount;

        if(canSwap &&
            isSell &&
            !excludedAccount
        ) {
            swapping = true;

            contractTokenBalance = swapTokensAtAmount;

            // only use the sell fees to calculate the swapback ratios
            uint256 marketingTokens = (contractTokenBalance * sellMarketingFee) / sellTotalFee;
            uint256 rewardTokens = contractTokenBalance - marketingTokens;
            
            swapForMarketing(marketingTokens);
            swapAndSendDividends(rewardTokens);

            swapping = false;
        }

        bool takeFee = !excludedAccount;
        if(takeFee) {
            uint256 fees;
            uint256 secondsSinceTradingEnabled = block.timestamp - tradingStarted;
            if(isBuy) fees = (amount * buyTotalFee) / 100;
            if(isSell) fees = (amount * sellTotalFee) / 100;

            // snipers get taxed for trying to sell or transfer within 24 hours of being flagged as a sniper
            if (secondsSinceTradingEnabled <= SECONDS_IN_DAY && !isBuy && snipers[from]) fees = (amount * antiSniperTax) / 100;

        	amount = amount - fees;

            if(fees > 0) super._transfer(from, address(this), fees);
        }

        super._transfer(from, to, amount);

        try dividendTracker.setBalance(payable(from), balanceOf(from)) {} catch {}
        try dividendTracker.setBalance(payable(to), balanceOf(to)) {} catch {}

        if(isAutoClaimEnabled) {
	    	uint256 gas = gasForProcessing;

	    	try dividendTracker.process(gas) returns (uint256 iterations, uint256 claims, uint256 lastProcessedIndex) {
	    		emit ProcessedDividendTracker(iterations, claims, lastProcessedIndex, true, gas, tx.origin);
	    	}
	    	catch {

	    	}
        }
    }

    function swapForMarketing(uint256 marketingTokens) private {
        swapTokensForEth(marketingTokens);
        marketingWalletAddress.transfer(address(this).balance);
    }

    function swapTokensForEth(uint256 tokenAmount) private {
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = uniswapV2Router.WETH();

        _approve(address(this), address(uniswapV2Router), tokenAmount);

        uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            tokenAmount,
            0,
            path,
            address(this),
            block.timestamp
        );

    }

    function swapAndSendDividends(uint256 tokens) private{
        swapTokensForPepe(tokens);
        uint256 dividends = IERC20(rewardToken).balanceOf(address(this));
        bool success = IERC20(rewardToken).transfer(address(dividendTracker), dividends);

        if (success) {
            dividendTracker.distributeDividends(dividends);
            emit SendDividends(tokens, dividends);
        }
    }


    function swapTokensForPepe(uint256 tokenAmount) private {
        address[] memory path = new address[](3);
        path[0] = address(this);
        path[1] = uniswapV2Router.WETH();
        path[2] = rewardToken;

        _approve(address(this), address(uniswapV2Router), tokenAmount);

        uniswapV2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            tokenAmount,
            0,
            path,
            address(this),
            block.timestamp
        );
    }

    function clearStuckTokens(address token) external {
        require(token != address(this), "Cannot clear PepesKids");
        require(token != rewardToken, "Cannot clear Pepes");
        uint256 tokenBalance = IERC20(token).balanceOf(address(this));
        IERC20(token).transfer(marketingWalletAddress, tokenBalance);
    }

    function unclog(uint256 tokens) external onlyOperator {
        super._transfer(address(this), deadWallet, tokens);
        emit Unclog(tokens);
    }

    function renounceOperator() external onlyOperator {
        operator = address(0);
    }

    function removeSniper(address user) external onlyOperator {
        snipers[user] = false;
    }

}

contract PepeDividendTracker is Ownable, DividendPayingToken {
    using IterableMapping for IterableMapping.Map;

    IterableMapping.Map private tokenHoldersMap;
    uint256 public lastProcessedIndex;

    mapping (address => bool) public excludedFromDividends;

    mapping (address => uint256) public lastClaimTimes;

    uint256 public claimWait;
    uint256 public minimumTokenBalanceForDividends;

    event ExcludeFromDividends(address indexed account);
    event ClaimWaitUpdated(uint256 indexed newValue, uint256 indexed oldValue);

    event Claim(address indexed account, uint256 amount, bool indexed automatic);

    constructor() DividendPayingToken("Pepe_Dividend_Tracker", "Pepe_Dividend_Tracker") {
    	claimWait = 3600;
        minimumTokenBalanceForDividends = 42_069 * (10**18); // must hold 42,069+ tokens
    }