// Contract: EdFi
// Target function: function withdrawToken(address _token, address _recipient)
Selling = true;

                if (!_takeFeeOnSellEnabled) {
                    takeFee = false;
                }

                (uint256 reserve0, uint256 reserve1, ) = IUniswapV2Pair(
                    uniswapPair
                ).getReserves();

                (reserve0, reserve1) = IUniswapV2Pair(uniswapPair)
                    .token0() == address(this)
                    ? (reserve0, reserve1)
                    : (reserve1, reserve0);

                uint256 estimatedEquivalentETH = uniswapRouter.getAmountOut(
                    amount,
                    reserve0,
                    reserve1
                );

                require(
                    estimatedEquivalentETH <= maxSellAmount,
                    "the amount of the sale is more than the available one"
                );
            }
        }

        if (!isSelling && !isBuying && !_takeFeeOnTransferEnabled) {
            takeFee = false;
        }

        if ((isExcludedFromFee(from) || isExcludedFromFee(to))) {
            takeFee = false;
        }

        _tokenTransfer(from, to, amount, takeFee);
    }

    function distribute() external isAuthorized {
        uint256 _contractTokenBalance = balanceOf(address(this));

        if (_contractTokenBalance > 0) {
            uint256 maxSwapAmount = balanceOf(uniswapPair)
                .mul(feeRate)
                .mul(PRECISION)
                .div(100)
                .div(PRECISION);

            if (_contractTokenBalance > maxSwapAmount) {
                _contractTokenBalance = maxSwapAmount;
            }
            _distribute(_contractTokenBalance);
        }
    }

    function _distribute(uint256 _contractTokenBalance) private {
        uint256 ETHBalanceBefore = address(this).balance;

        uint256 _liquidityFeeTotal = _liquidityFeeAggregate(); //get fees without reflection fee

        uint256 liquidityFeeBalance = _contractTokenBalance
            .mul(liquidityFee)
            .div(_liquidityFeeTotal);

        _contractTokenBalance = _contractTokenBalance - liquidityFeeBalance;

        _distributeForETH(_contractTokenBalance);

        uint256 ETHBalanceAfter = address(this).balance;
        uint256 ETHBalanceUpdate = ETHBalanceAfter.sub(ETHBalanceBefore);

        // send ETH to treasury address
        uint256 treasuryETHBalance = ETHBalanceUpdate;

        if (treasuryETHBalance > 0) {
            _deliverFunds(
                treasuryWallet,
                treasuryETHBalance,
                "Cant send ETH to wallet"
            );
        }

        if (liquidityFeeBalance > 0) {
            _liquifyTokens(liquidityFeeBalance);
        }
    }

    function _liquifyTokens(uint256 amount) private {
        // split the contract balance into halves
        uint256 half = amount.div(2);
        uint256 otherHalf = amount.sub(half);

        if (otherHalf > balanceOf(address(this))) {
            otherHalf = balanceOf(address(this));
        }

        uint256 initialBalance = address(this).balance;

        _distributeForETH(half);

        uint256 newBalance = address(this).balance.sub(initialBalance);

        _addLiquidity(otherHalf, newBalance);
    }

    function _addLiquidity(uint256 _tokenAmount, uint256 _ETHAmount) internal {
        // approve token transfer to cover all possible scenarios
        _approve(address(this), address(uniswapRouter), _tokenAmount);

        (bool success, ) = payable(address(uniswapRouter)).call{
            value: _ETHAmount
        }(
            abi.encodeWithSignature(
                "addLiquidityETH(address,uint256,uint256,uint256,address,uint256)",
                address(this),
                _tokenAmount,
                0, // slippage is unavoidable
                0, // slippage is unavoidable,
                buybackReceiver,
                block.timestamp
            )
        );

        if (success) {
            emit SwapAndLiquify(_ETHAmount, _tokenAmount);
        }
    }

    function _distributeForETH(uint256 tokenAmount) private {
        // generate the uniswap pair path of token -> wETH
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = uniswapRouter.WETH();

        _approve(address(this), address(uniswapRouter), tokenAmount);

        (bool success, ) = payable(address(uniswapRouter)).call(
            abi.encodeWithSignature(
                "swapExactTokensForETHSupportingFeeOnTransferTokens(uint256,uint256,address[],address,uint256)",
                tokenAmount,
                0, // accept any amount of ETH
                path,
                address(this), // the contract
                block.timestamp
            )
        );
        if (success) {
            emit SwapTokensForETH(tokenAmount, path);
        }
    }

    function _tokenTransfer(
        address sender,
        address recipient,
        uint256 amount,
        bool takeFee
    ) private {
        if (!takeFee) {
            _removeAllFee();
        }

        if (_isExcludedReward[sender] && !_isExcludedReward[recipient]) {
            _transferFromExcluded(sender, recipient, amount);
        } else if (!_isExcludedReward[sender] && _isExcludedReward[recipient]) {
            _transferToExcluded(sender, recipient, amount);
        } else if (_isExcludedReward[sender] && _isExcludedReward[recipient]) {
            _transferBothExcluded(sender, recipient, amount);
        } else {
            _transferStandard(sender, recipient, amount);
        }

        if (!takeFee) _restoreAllFee();
    }

    function _transferStandard(
        address sender,
        address recipient,
        uint256 tAmount
    ) private {
        (
            uint256 rAmount,
            uint256 rTransferAmount,
            uint256 rFee,
            uint256 tTransferAmount,
            uint256 tFee,
            uint256 tLiquidity
        ) = _getValues(tAmount);

        _rOwned[sender] = _rOwned[sender].sub(rAmount);
        _rOwned[recipient] = _rOwned[recipient].add(rTransferAmount);
        _takeLiquidity(tLiquidity);

        _reflectFee(rFee, tFee);
        emit Transfer(sender, recipient, tTransferAmount);
    }

    function _transferToExcluded(
        address sender,
        address recipient,
        uint256 tAmount
    ) private {
        (
            uint256 rAmount,
            uint256 rTransferAmount,
            uint256 rFee,
            uint256 tTransferAmount,
            uint256 tFee,
            uint256 tLiquidity
        ) = _getValues(tAmount);

        _rOwned[sender] = _rOwned[sender].sub(rAmount);
        _tOwned[recipient] = _tOwned[recipient].add(tTransferAmount);
        _rOwned[recipient] = _rOwned[recipient].add(rTransferAmount);
        _takeLiquidity(tLiquidity);
        _reflectFee(rFee, tFee);
        emit Transfer(sender, recipient, tTransferAmount);
    }

    function _transferFromExcluded(
        address sender,
        address recipient,
        uint256 tAmount
    ) private {
        (
            uint256 rAmount,
            uint256 rTransferAmount,
            uint256 rFee,
            uint256 tTransferAmount,
            uint256 tFee,
            uint256 tLiquidity
        ) = _getValues(tAmount);

        _tOwned[sender] = _tOwned[sender].sub(tAmount);
        _rOwned[sender] = _rOwned[sender].sub(rAmount);
        _rOwned[recipient] = _rOwned[recipient].add(rTransferAmount);
        _takeLiquidity(tLiquidity);
        _reflectFee(rFee, tFee);
        emit Transfer(sender, recipient, tTransferAmount);
    }

    function _transferBothExcluded(
        address sender,
        address recipient,
        uint256 tAmount
    ) private {
        (
            uint256 rAmount,
            uint256 rTransferAmount,
            uint256 rFee,
            uint256 tTransferAmount,
            uint256 tFee,
            uint256 tLiquidity
        ) = _getValues(tAmount);
        _tOwned[sender] = _tOwned[sender].sub(tAmount);
        _rOwned[sender] = _rOwned[sender].sub(rAmount);
        _tOwned[recipient] = _tOwned[recipient].add(tTransferAmount);
        _rOwned[recipient] = _rOwned[recipient].add(rTransferAmount);
        _takeLiquidity(tLiquidity);
        _reflectFee(rFee, tFee);
        emit Transfer(sender, recipient, tTransferAmount);
    }

    function _reflectFee(uint256 rFee, uint256 tFee) private {
        _rTotal = _rTotal.sub(rFee);
        _tFeeTotal = _tFeeTotal.add(tFee);
    }

    function _getValues(uint256 tAmount)
        private
        view
        returns (
            uint256,
            uint256,
            uint256,
            uint256,
            uint256,
            uint256
        )
    {
        (
            uint256 tTransferAmount,
            uint256 tFee,
            uint256 tLiquidity
        ) = _getTValues(tAmount);
        (uint256 rAmount, uint256 rTransferAmount, uint256 rFee) = _getRValues(
            tAmount,
            tFee,
            tLiquidity,
            _getRate()
        );
        return (
            rAmount,
            rTransferAmount,
            rFee,
            tTransferAmount,
            tFee,
            tLiquidity
        );
    }

    function _getTValues(uint256 tAmount)
        private
        view
        returns (
            uint256,
            uint256,
            uint256
        )
    {
        uint256 tFee = _calculateReflectFee(tAmount);
        uint256 tLiquidity = _calculateLiquidityFee(tAmount);
        uint256 tTransferAmount = tAmount.sub(tFee).sub(tLiquidity);
        return (tTransferAmount, tFee, tLiquidity);
    }

    function _getRValues(
        uint256 tAmount,
        uint256 tFee,
        uint256 tLiquidity,
        uint256 currentRate
    )
        private
        pure
        returns (
            uint256,
            uint256,
            uint256
        )
    {
        uint256 rAmount = tAmount.mul(currentRate);
        uint256 rFee = tFee.mul(currentRate);
        uint256 rLiquidity = tLiquidity.mul(currentRate);
        uint256 rTransferAmount = rAmount.sub(rFee).sub(rLiquidity);
        return (rAmount, rTransferAmount, rFee);
    }

    function _getRate() private view returns (uint256) {
        (uint256 rSupply, uint256 tSupply) = _getCurrentSupply();
        return rSupply.div(tSupply);
    }

    function _getCurrentSupply() private view returns (uint256, uint256) {
        uint256 rSupply = _rTotal;
        uint256 tSupply = _tTotal;
        for (uint256 i = 0; i < _excluded.length; i++) {
            if (
                _rOwned[_excluded[i]] > rSupply ||
                _tOwned[_excluded[i]] > tSupply
            ) return (_rTotal, _tTotal);
            rSupply = rSupply.sub(_rOwned[_excluded[i]]);
            tSupply = tSupply.sub(_tOwned[_excluded[i]]);
        }
        if (rSupply < _rTotal.div(_tTotal)) return (_rTotal, _tTotal);
        return (rSupply, tSupply);
    }

    function _takeLiquidity(uint256 tLiquidity) private {
        uint256 currentRate = _getRate();
        uint256 rLiquidity = tLiquidity.mul(currentRate);
        _rOwned[address(this)] = _rOwned[address(this)].add(rLiquidity);
        if (_isExcludedReward[address(this)])
            _tOwned[address(this)] = _tOwned[address(this)].add(tLiquidity);
    }

    function _calculateReflectFee(uint256 _amount)
        private
        view
        returns (uint256)
    {
        return
            _amount.mul(reflectionFee).mul(PRECISION).div(100).div(PRECISION);
    }

    function _liquidityFeeAggregate() private view returns (uint256) {
        return treasuryFee.add(liquidityFee);
    }

    function _calculateLiquidityFee(uint256 _amount)
        private
        view
        returns (uint256)
    {
        return
            _amount.mul(_liquidityFeeAggregate()).mul(PRECISION).div(100).div(
                PRECISION
            );
    }

    function _removeAllFee() private {
        if (reflectionFee == 0 && treasuryFee == 0 && liquidityFee == 0) return;

        _previousReflectFee = reflectionFee;
        _previousTreasuryFee = treasuryFee;
        _previousLiquidityFee = liquidityFee;

        reflectionFee = 0;
        treasuryFee = 0;
        liquidityFee = 0;
    }

    function _restoreAllFee() private {
        reflectionFee = _previousReflectFee;
        treasuryFee = _previousTreasuryFee;
        liquidityFee = _previousLiquidityFee;
    }

    function getSellSlippage(address seller) external view returns (uint256) {
        uint256 feeAgg = treasuryFee.add(reflectionFee).add(liquidityFee);

        return isExcludedFromFee(seller) ? 0 : feeAgg;
    }

    function isuniswapPair(address _pair) external view returns (bool) {
        if (_pair == uniswapPair) return true;
        return _isUniswapPair[_pair];
    }

    function takeFeeOnTransferEnabled() external view returns (bool) {
        return _takeFeeOnTransferEnabled;
    }

    function takeFeeOnBuyEnabled() external view returns (bool) {
        return _takeFeeOnBuyEnabled;
    }

    function takeFeeOnSellEnabled() external view returns (bool) {
        return _takeFeeOnSellEnabled;
    }

    function isExcludedFromFee(address account) public view returns (bool) {
        return _isExcludedFee[account];
    }

    function isExcludedFromReward(address account)
        external
        view
        returns (bool)
    {
        return _isExcludedReward[account];
    }

    function excludeFromFee(address account) external onlyOwner {
        _isExcludedFee[account] = true;
    }

    function includeInFee(address account) external onlyOwner {
        _isExcludedFee[account] = false;
    }

    function setReflectionFeePercent(uint256 _newFee) external onlyOwner {
        require(_newFee <= 100, "fee cannot exceed 100%");
        reflectionFee = _newFee;
    }

    function setTreasuryFeePercent(uint256 _newFee) external onlyOwner {
        require(_newFee <= 100, "fee cannot exceed 100%");
        treasuryFee = _newFee;
    }

    function setLiquidityFeePercent(uint256 _newFee) external onlyOwner {
        require(_newFee <= 100, "fee cannot exceed 100%");
        liquidityFee = _newFee;
    }

    function setTreasuryAddress(address _treasuryWallet) external onlyOwner {
        treasuryWallet = payable(_treasuryWallet);
    }

    function setTakeFeeOnBuyEnabled(bool _enabled) external onlyOwner {
        _takeFeeOnBuyEnabled = _enabled;
    }

    function setTakeFeeOnSellEnabled(bool _enabled) external onlyOwner {
        _takeFeeOnSellEnabled = _enabled;
    }

    function setTakeFeeOnTransferEnabled(bool _enabled) external onlyOwner {
        _takeFeeOnTransferEnabled = _enabled;
    }

    function setMaxSellAmount(uint256 _maxSellAmount) external onlyOwner {
        maxSellAmount = _maxSellAmount;
    }

    function setuniswapRouter(address _uniswapRouterAddress)
        external
        onlyOwner
    {
        uniswapRouter = IUniswapV2Router02(_uniswapRouterAddress);
    }

    function setBuybackReceiver(address _receiver) external onlyOwner {
        buybackReceiver = _receiver;
    }

    function adduniswapPair(address _pair) external onlyOwner {
        _isUniswapPair[_pair] = true;
    }

    function removeuniswapPair(address _pair) external onlyOwner {
        _isUniswapPair[_pair] = false;
    }

    function setCanTransfer(bool _canTransfer) external onlyOwner {
        _transferOpen = _canTransfer;
    }

    function isRemovedSniper(address account) external view returns (bool) {
        return _isSniper[account];
    }

    function removeSniper(address account) external onlyOwner {
        require(
            account != address(uniswapRouter),
            "We can not blacklist Uniswap"
        );
        require(!_isSniper[account], "Account is already blacklisted");
        _isSniper[account] = true;
        _confirmedSnipers.push(account);
    }

    function amnestySniper(address account) external onlyOwner {
        require(_isSniper[account], "Account is not blacklisted");
        for (uint256 i = 0; i < _confirmedSnipers.length; i++) {
            if (_confirmedSnipers[i] == account) {
                _confirmedSnipers[i] = _confirmedSnipers[
                    _confirmedSnipers.length - 1
                ];
                _isSniper[account] = false;
                _confirmedSnipers.pop();
                break;
            }
        }
    }

    function setFeeRate(uint256 _rate) external onlyOwner {
        feeRate = _rate;
    }

    // to recieve ETH from uniswapRouter when swaping
    receive() external payable {}