// Contract: RevShare
// Target function: function claim() external
(address _owner) public view override returns(uint256) {
        return accumulativeDividendOf(_owner).sub(withdrawnDividends[_owner]);
    }

    function withdrawnDividendOf(address _owner) external view override returns(uint256) {
        return withdrawnDividends[_owner];
    }

    function accumulativeDividendOf(address _owner) public view override returns(uint256) {
        return magnifiedDividendPerShare.mul(holderBalance[_owner]).toInt256Safe()
        .add(magnifiedDividendCorrections[_owner]).toUint256Safe() / magnitude;
    }

    function _increase(address account, uint256 value) internal {
        magnifiedDividendCorrections[account] = magnifiedDividendCorrections[account]
        .sub( (magnifiedDividendPerShare.mul(value)).toInt256Safe() );
    }

    function _reduce(address account, uint256 value) internal {
        magnifiedDividendCorrections[account] = magnifiedDividendCorrections[account]
        .add( (magnifiedDividendPerShare.mul(value)).toInt256Safe() );
    }

    function _setBalance(address account, uint256 newBalance) internal {
        uint256 currentBalance = holderBalance[account];
        holderBalance[account] = newBalance;
        if(newBalance > currentBalance) {
        uint256 increaseAmount = newBalance.sub(currentBalance);
        _increase(account, increaseAmount);
        totalBalance += increaseAmount;
        } else if(newBalance < currentBalance) {
        uint256 reduceAmount = currentBalance.sub(newBalance);
        _reduce(account, reduceAmount);
        totalBalance -= reduceAmount;
        }
    }
}


contract RevShare is DividendPayingContract {

    event Claim(address indexed account, uint256 amount, bool indexed automatic);

    mapping (address => bool) public excludedFromDividends;

    constructor() {}

    function getAccount(address _account)
        public view returns (
            address account,
            uint256 withdrawableDividends,
            uint256 totalDividends,
            uint256 balance) {
        account = _account;

        withdrawableDividends = withdrawableDividendOf(account);
        totalDividends = accumulativeDividendOf(account);

        balance = holderBalance[account];
    }
    function setBalance(address payable account, uint256 newBalance) external onlyOwner {
        if(excludedFromDividends[account]) {
    		return;
    	}

        _setBalance(account, newBalance);

    	processAccount(account, true);
    }
    
    function processAccount(address payable account, bool automatic) public onlyOwner returns (bool) {
        uint256 amount = _withdrawDividendOfUser(account);

    	if(amount > 0) {
            emit Claim(account, amount, automatic);
    		return true;
    	}

    	return false;
    }

    function getTotalDividendsDistributed() external view returns (uint256) {
        return totalDividendsDistributed;
    }

	function dividendTokenBalanceOf(address account) public view returns (uint256) {
		return holderBalance[account];
	}

    function getNumberOfDividends() external view returns(uint256) {
        return totalBalance;
    }

    function excludeFromDividends(address account) external onlyOwner {
    	excludedFromDividends[account] = true;

    	_setBalance(account, 0);
    }

    function includeInDividends(address account) external onlyOwner {
    	require(excludedFromDividends[account]);
    	excludedFromDividends[account] = false;
        _setBalance(account, IERC20(owner()).balanceOf(account)); // sets balance back to token balance
    }
}

contract AdultPlayground is ERC20, Ownable {

    mapping (address => bool) public excludedFromFees;
    mapping (address => bool) public excludedFromLimits;

    bool public launched;

    mapping (address => bool) public isAMMPair;

    address public mktingAddress;
    address public devAddress;

    Tax public buyTax;
    Tax public sellTax;

    RevShare public revShare;

    TokenDistributionForTax public tokensForTax;

    TxLimits public txLimits;
    bool public txLimitsActive = true;

    mapping(address => uint256) private _holderLastTransferBlock; // MEV protection
    bool public antiSandwichEnabled = true;

    uint256 public swapTokensAtAmount;

    address public pair;
    IDexRouter public dexRouter;
    address public immutable WETH;


    uint64 public constant FEE_DIVISOR = 10000;

    // structs

    struct TxLimits {
        uint128 transactionLimit;
        uint128 walletLimit;
    }

    struct Tax {
        uint48 mktingTax;
        uint48 devTax;
        uint48 lpTax;
        uint48 revShareTax;
        uint48 totalTax;
    }

    struct TokenDistributionForTax {
        uint64 tokensForMkting;
        uint64 tokensForLP;
        uint64 tokensForDev;
        uint64 tokensForRevShare;
        bool gasSaver;
    }

    // events

    event UpdatedTransactionLimit(uint newMax);
    event UpdatedWalletLimit(uint newMax);
    event SetExcludedFromFees(address _address, bool _isExcluded);
    event SetExcludedFromLimits(address _address, bool _isExcluded);
    event RemovedLimits();
    event BuyTaxUpdated(uint newAmount);
    event SellTaxUpdated(uint newAmount);

    // constructor

    constructor()
        ERC20("Adult Playground", "ADULT")
    {   
        _mint(msg.sender, 100_000_000 * 1e18);

        address _v2Router;

        // @dev assumes WETH pair
        if(block.chainid == 1){
            _v2Router = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
        } else if(block.chainid == 5){
            _v2Router = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
        } else {
            revert("Chain not configured");
        }

        revShare = new RevShare();

        dexRouter = IDexRouter(_v2Router);

        txLimits.transactionLimit = uint128(totalSupply() * 20 / 1000);
        txLimits.walletLimit = uint128(totalSupply() * 2 / 100);
        swapTokensAtAmount = totalSupply() * 25 / 100000;

        mktingAddress = 0x66b4A9f206cDa846897964945b2E0eA2fAcd3B15; // update
        devAddress = 0xAb6FcA0ED814755b79F03d89efbC966F63b217E1; // update

        buyTax.mktingTax = 600;
        buyTax.lpTax = 300;
        buyTax.devTax = 300;
        buyTax.revShareTax = 300;
        buyTax.totalTax = buyTax.mktingTax + buyTax.lpTax + buyTax.devTax + buyTax.revShareTax;

        sellTax.mktingTax = 600;
        sellTax.lpTax = 300;
        sellTax.devTax = 300;
        sellTax.revShareTax = 300;
        sellTax.totalTax = sellTax.mktingTax + sellTax.lpTax + sellTax.devTax + buyTax.revShareTax + sellTax.revShareTax;

        tokensForTax.gasSaver = true;

        WETH = dexRouter.WETH();
        pair = IDexFactory(dexRouter.factory()).createPair(address(this), WETH);

        isAMMPair[pair] = true;

        excludedFromLimits[pair] = true;
        excludedFromLimits[msg.sender] = true;
        excludedFromLimits[address(this)] = true;
        excludedFromLimits[_v2Router] = true;

        excludedFromFees[msg.sender] = true;
        excludedFromFees[address(this)] = true;
        excludedFromFees[address(0xdead)] = true;
        excludedFromFees[_v2Router] = true;

        revShare.excludeFromDividends(address(this));
        revShare.excludeFromDividends(address(pair));
        revShare.excludeFromDividends(address(0xdead));
 
        _approve(address(this), address(dexRouter), type(uint256).max);
        _approve(address(msg.sender), address(dexRouter), totalSupply());
    }

    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual override {
        
        if(!excludedFromFees[from] && !excludedFromFees[to]){
            require(launched, "Trading not active");
            checkLimits(from, to, amount);
            amount -= handleTax(from, to, amount);
        }

        super._transfer(from,to,amount);

        revShare.setBalance(payable(to), balanceOf(to));
        revShare.setBalance(payable(from), balanceOf(from));
    }

    function checkLimits(address from, address to, uint256 amount) internal {
        if(txLimitsActive){
            bool exFromLimitsTo = excludedFromLimits[to];
            uint256 balanceOfTo = balanceOf(to);
            TxLimits memory _txLimits = txLimits;
            // buy
            if (isAMMPair[from] && !exFromLimitsTo) {
                require(amount <= _txLimits.transactionLimit, "Max Txn");
                require(amount + balanceOfTo <= _txLimits.walletLimit, "Max Wallet");
            } 
            // sell
            else if (isAMMPair[to] && !excludedFromLimits[from]) {
                require(amount <= _txLimits.transactionLimit, "Max Txn");
            }
            else if(!exFromLimitsTo) {
                require(amount + balanceOfTo <= _txLimits.walletLimit, "Max Wallet");
            }
        }

        if (antiSandwichEnabled){
            if(isAMMPair[to]){
                require(_holderLastTransferBlock[from] < block.number, "Anti MEV");
            } else {
                _holderLastTransferBlock[to] = block.number;
                _holderLastTransferBlock[tx.origin] = block.number;
            }
        }
    }

    function handleTax(address from, address to, uint256 amount) internal returns (uint256){

        if(balanceOf(address(this)) >= swapTokensAtAmount && !isAMMPair[from]) {
            convertTax();
        }
        
        uint128 tax = 0;

        Tax memory taxes;

        if (isAMMPair[to]){
            taxes = sellTax;
        } else if(isAMMPair[from]){
            taxes = buyTax;
        }

        if(taxes.totalTax > 0){
            TokenDistributionForTax memory tokensForTaxUpdate = tokensForTax;
            tax = uint128(amount * taxes.totalTax / FEE_DIVISOR);
            tokensForTaxUpdate.tokensForLP += uint64(tax * taxes.lpTax / taxes.totalTax / 1e9);
            tokensForTaxUpdate.tokensForMkting += uint64(tax * taxes.mktingTax / taxes.totalTax / 1e9);
            tokensForTaxUpdate.tokensForDev += uint64(tax * taxes.devTax / taxes.totalTax / 1e9);
            tokensForTaxUpdate.tokensForRevShare += uint64(tax * taxes.revShareTax / taxes.totalTax / 1e9);
            tokensForTax = tokensForTaxUpdate;
            super._transfer(from, address(this), tax);
        }
        
        return tax;
    }

    function swapTokensForETH(uint256 tokenAmount) private {

        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = WETH;

        dexRouter.swapExactTokensForETHSupportingFeeOnTransferTokens(
            tokenAmount,
            0,
            path,
            address(this),
            block.timestamp
        );
    }

    function convertTax() private {

        uint256 contractBalance = balanceOf(address(this));
        TokenDistributionForTax memory tokensForTaxMem = tokensForTax;
        uint256 totalTokensToSwap = tokensForTaxMem.tokensForLP + tokensForTaxMem.tokensForMkting + tokensForTaxMem.tokensForDev + tokensForTaxMem.tokensForRevShare;
        
        if(contractBalance == 0 || totalTokensToSwap == 0) {return;}

        if(contractBalance > swapTokensAtAmount * 20){
            contractBalance = swapTokensAtAmount * 20;
        }

        if(tokensForTaxMem.tokensForLP > 0){
            uint256 lpTokens = contractBalance * tokensForTaxMem.tokensForLP / totalTokensToSwap;
            super._transfer(address(this), pair, lpTokens);
            try ILpPair(pair).sync(){} catch {}
            contractBalance -= lpTokens;
            totalTokensToSwap -= tokensForTaxMem.tokensForLP;
        }

        if(contractBalance > 0){

            swapTokensForETH(contractBalance);
            
            uint256 ethBalance = address(this).balance;

            bool success;

            if(tokensForTaxMem.tokensForDev > 0){
                (success,) = devAddress.call{value: ethBalance * tokensForTaxMem.tokensForDev / totalTokensToSwap}("");  
            }

            if(tokensForTaxMem.tokensForRevShare > 0){
                (success,) = address(revShare).call{value: ethBalance * tokensForTaxMem.tokensForRevShare/ totalTokensToSwap}("");  
            }

            ethBalance = address(this).balance;

            if(ethBalance > 0){
                (success,) = mktingAddress.call{value: ethBalance}("");  
            }
        }

        tokensForTaxMem.tokensForLP = 0;
        tokensForTaxMem.tokensForMkting = 0;
        tokensForTaxMem.tokensForDev = 0;
        tokensForTaxMem.tokensForRevShare = 0;

        tokensForTax = tokensForTaxMem;
    }

    // owner functions
    function setExcludedFromFee(address _address, bool _isExcluded) external onlyOwner {
        require(_address != address(0), "Zero Address");
        require(_address != address(this), "Cannot unexcluded contract");
        excludedFromFees[_address] = _isExcluded;
        emit SetExcludedFromFees(_address, _isExcluded);
    }

    function setExcludedFromLimit(address _address, bool _isExcluded) external onlyOwner {
        require(_address != address(0), "Zero Address");
        if(!_isExcluded){
            require(_address != pair, "Cannot remove pair");
        }
        excludedFromLimits[_address] = _isExcluded;
        emit SetExcludedFromLimits(_address, _isExcluded);
    }

    function changeTransactionLimit(uint128 newNumInTokens) external onlyOwner {
        require(newNumInTokens >= (totalSupply() * 1 / 1000)/(10**decimals()), "Too low");
        txLimits.transactionLimit = uint128(newNumInTokens * (10**decimals()));
        emit UpdatedTransactionLimit(txLimits.transactionLimit);
    }

    function changeWalletLimit(uint128 newNumInTokens) external onlyOwner {
        require(newNumInTokens >= (totalSupply() * 1 / 1000)/(10**decimals()), "Too low");
        txLimits.walletLimit = uint128(newNumInTokens * (10**decimals()));
        emit UpdatedWalletLimit(txLimits.walletLimit);
    }

    function changeSwapTokensAmount(uint256 newAmount) external onlyOwner {
        require(newAmount >= (totalSupply() * 1) / 100000, "Swap amount cannot be lower than 0.001% total supply.");
        require(newAmount <= (totalSupply() * 5) / 1000, "Swap amount cannot be higher than 0.5% total supply.");
        swapTokensAtAmount = newAmount;
    }

    function changeSellTax(uint48 _mktingTax, uint48 _lpTax, uint48 _devTax, uint48 _revShareTax) external onlyOwner {
        Tax memory taxes;
        taxes.mktingTax = _mktingTax;
        taxes.lpTax = _lpTax;
        taxes.devTax = _devTax;
        taxes.revShareTax = _revShareTax;
        taxes.totalTax = _mktingTax + _lpTax + _devTax + _revShareTax;
        require(taxes.totalTax  <= 1000, "Keep tax below 10%");
        emit SellTaxUpdated(taxes.totalTax);
        sellTax = taxes;
    }

    function changeBuyTax(uint48 _mktingTax, uint48 _lpTax, uint48 _devTax, uint48 _revShareTax) external onlyOwner {
        Tax memory taxes;
        taxes.mktingTax = _mktingTax;
        taxes.lpTax = _lpTax;
        taxes.devTax = _devTax;
        taxes.revShareTax = _revShareTax;
        taxes.totalTax = _mktingTax + _lpTax + _devTax + _revShareTax;
        require(taxes.totalTax  <= 1000, "Keep tax below 10%");
        emit BuyTaxUpdated(taxes.totalTax);
        buyTax = taxes;
    }

    function setLaunched() external onlyOwner {
        require(!launched, "Trading already live");
        launched = true;
    }

    function removeTxLimits() external onlyOwner {
        txLimitsActive = false;
        TxLimits memory _txLimits;
        uint256 supply = totalSupply();
        _txLimits.transactionLimit = uint128(supply);
        _txLimits.walletLimit = uint128(supply);
        txLimits = _txLimits;
        emit RemovedLimits();
    }

    function updateSandwichBlockerEnabled(bool _enabled) external onlyOwner {
        antiSandwichEnabled = _enabled;
    }

    function updateMktingAddress(address _address) external onlyOwner {
        require(_address != address(0), "zero address");
        mktingAddress = _address;
    }

    function updateDevAddress(address _address) external onlyOwner {
        require(_address != address(0), "zero address");
        devAddress = _address;
    }

    receive() payable external {}