contract QUADRANTToken is Context, IERC20, Ownable {
    using Address for address payable;
    mapping(address => uint256) private _rOwned;
    mapping(address => uint256) private _tOwned;
    mapping(address => mapping(address => uint256)) private _allowances;
    mapping(address => bool) private _isExcludedFromFee;
    mapping(address => bool) private _isExcludedFromReflection;
    mapping(address => bool) private _isExcludedFromMaxWallet;

    mapping(address => bool) public isBot;

    address[] private _excluded;

    uint8 private constant _decimals = 18;
    uint256 private constant MAX = ~uint256(0);

    uint256 private _tTotal = 1_000_000_000_000_000 * 10**_decimals;
    uint256 private _rTotal = (MAX - (MAX % _tTotal));

    uint256 public maxTxAmountBuy = _tTotal / 1000; // 10% of supply
    uint256 public maxTxAmountSell = _tTotal / 1000; // 10% of supply
    uint256 public maxWalletAmount = _tTotal / 1;

    uint256 public maxBurn = _tTotal * 3 / 10; //Burn up to 30% of supply

    //antisnipers
    uint256 public liqAddedBlockNumber;
    uint256 public blocksToWait = 1;

    address public rewardsAddress = 0xd934EBf314cbC46f52E10Fa2f78d7AA5A4aE6fa4;
    address public devAddress = 0xd941bA78146B6e198ad8d82EF7e2dA6042334733;
    address public burnAddress = 0x000000000000000000000000000000000000dEaD;
    address public lpAddress = 0x092e4FFCbf780561E8513cBD6e231721596a9DF3;

    mapping(address => bool) public isAutomatedMarketMakerPair;

    string private constant _name = "QUADRANT Token";
    string private constant _symbol = "QUADRANT";

    bool private inSwapAndLiquify;

    // Anti-Whale Settings
    uint256 public _whaleBuySellThreshold = 5_000_000_000 * 10**_decimals;
    mapping(address => uint256) private _amountBuy;
    mapping(address => uint256) private _amountSell;

    IUniswapV2Router02 public UniswapV2Router;
    address public uniswapPair;
    bool public swapAndLiquifyEnabled = true;
    uint256 public numTokensSellToAddToLiquidity = 5_000_000 * 10**_decimals;

    struct feeRatesStruct {
        uint8 rfi;
        uint8 reward;
        uint8 dev;
        uint8 lp;
        uint8 burn;
        uint8 toSwap;
    }

    feeRatesStruct public buySellRates =
        feeRatesStruct({
            rfi: 2, // 0 RFI rate, in %
            dev: 2, // dev team fee in %
            reward: 5, // reward fee in %
            lp: 2, // lp rate in %
            burn: 1,
            toSwap: 10 // reward + dev + lp + burn
        });

    feeRatesStruct public whaleRates =
        feeRatesStruct({
            rfi: 4, // 0 RFI rate, in %
            dev: 4, // dev team fee in %
            reward: 4, // reward fee in %
            lp: 4, // lp rate in %
            burn: 4,
            toSwap: 16 // reward + dev + lp + burn
        });

    feeRatesStruct private appliedRates = buySellRates;

    struct TotFeesPaidStruct {
        uint256 rfi;
        uint256 toSwap;
    }
    TotFeesPaidStruct public totFeesPaid;

    struct valuesFromGetValues {
        uint256 rAmount;
        uint256 rTransferAmount;
        uint256 rRfi;
        uint256 rToSwap;
        uint256 tTransferAmount;
        uint256 tRfi;
        uint256 tToSwap;
    }

    event SwapAndLiquifyEnabledUpdated(bool enabled);
    event SwapAndLiquify(
        uint256 tokensSwapped,
        uint256 ETHReceived,
        uint256 tokensIntotoSwap
    );
    event LiquidityAdded(uint256 tokenAmount, uint256 ETHAmount);
    event rewardAndDevFeesAdded(uint256 devFee, uint256 rewardFee);
    event SetAutomatedMarketMakerPair(address indexed pair, bool indexed value);
    event BlacklistedUser(address botAddress, bool indexed value);
    event MaxWalletAmountUpdated(uint256 amount);
    event ExcludeFromMaxWallet(address account, bool indexed isExcluded);

    modifier lockTheSwap() {
        inSwapAndLiquify = true;
        _;
        inSwapAndLiquify = false;
    }

    constructor() {
        IUniswapV2Router02 _UniswapV2Router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); // Uniswap V2

        uniswapPair = IUniswapV2Factory(_UniswapV2Router.factory()).createPair(
            address(this),
            _UniswapV2Router.WETH()
        );
        isAutomatedMarketMakerPair[uniswapPair] = true;
        emit SetAutomatedMarketMakerPair(uniswapPair, true);
        UniswapV2Router = _UniswapV2Router;

        _rOwned[owner()] = _rTotal;

        _isExcludedFromFee[owner()] = true;
        _isExcludedFromFee[rewardsAddress] = true;
        _isExcludedFromFee[devAddress] = true;
        _isExcludedFromFee[burnAddress] = true;
        _isExcludedFromFee[address(this)] = true;
        _isExcludedFromFee[lpAddress] = true;

        _isExcludedFromMaxWallet[owner()] = true;
        _isExcludedFromMaxWallet[address(this)] = true;
        _isExcludedFromMaxWallet[uniswapPair] = true;

        //Exclude these addresses from reflection
        _isExcludedFromReflection[owner()] = true;
        _isExcludedFromReflection[rewardsAddress] = true;
        _isExcludedFromReflection[devAddress] = true;
        _isExcludedFromReflection[burnAddress] = true;
        _isExcludedFromReflection[address(this)] = true;
        _isExcludedFromReflection[lpAddress] = true;

        _tOwned[owner()] = _tTotal;
        emit Transfer(address(0), owner(), _tTotal);
    }

    //std ERC20:
    function name() public pure returns (string memory) {
        return _name;
    }

    function symbol() public pure returns (string memory) {
        return _symbol;
    }

    function decimals() public pure returns (uint8) {
        return _decimals;
    }

    //override ERC20:
    function totalSupply() public view override returns (uint256) {
        return _tTotal;
    }

    function balanceOf(address account) public view override returns (uint256) {
        if (_isExcludedFromReflection[account]) return _tOwned[account];
        return tokenFromReflection(_rOwned[account]);
    }

    function transfer(address recipient, uint256 amount)
        public
        override
        returns (bool)
    {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    function allowance(address owner, address spender)
        public
        view
        override
        returns (uint256)
    {
        return _allowances[owner][spender];
    }

    function approve(address spender, uint256 amount)
        public
        override
        returns (bool)
    {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public override returns (bool) {
        _transfer(sender, recipient, amount);

        uint256 currentAllowance = _allowances[sender][_msgSender()];
        require(
            currentAllowance >= amount,
            "ERC20: transfer amount exceeds allowance"
        );
        unchecked {
            _approve(sender, _msgSender(), currentAllowance - amount);
        }

        return true;
    }

    function increaseAllowance(address spender, uint256 addedValue)
        public
        virtual
        returns (bool)
    {
        _approve(
            _msgSender(),
            spender,
            _allowances[_msgSender()][spender] + addedValue
        );
        return true;
    }

    function decreaseAllowance(address spender, uint256 subtractedValue)
        public
        virtual
        returns (bool)
    {
        uint256 currentAllowance = _allowances[_msgSender()][spender];
        require(
            currentAllowance >= subtractedValue,
            "ERC20: decreased allowance below zero"
        );
        unchecked {
            _approve(_msgSender(), spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    function isExcludedFromReward(address account) public view returns (bool) {
        return _isExcludedFromReflection[account];
    }

    function reflectionFromToken(uint256 tAmount, bool deductTransferRfi)
        public
        view
        returns (uint256)
    {
        require(tAmount <= _tTotal, "Amount must be less than supply");
        if (!deductTransferRfi) {
            valuesFromGetValues memory s = _getValues(tAmount, true);
            return s.rAmount;
        } else {
            valuesFromGetValues memory s = _getValues(tAmount, true);
            return s.rTransferAmount;
        }
    }

    function tokenFromReflection(uint256 rAmount)
        public
        view
        returns (uint256)
    {
        require(
            rAmount <= _rTotal,
            "Amount must be less than total reflections"
        );
        uint256 currentRate = _getRate();
        return rAmount / currentRate;
    }

    //No current rfi - Tiered Rewarding Feature Applied at APP Launch
    function excludeFromReward(address account) external onlyOwner {
        require(!_isExcludedFromReflection[account], "Account is already excluded");
        if (_rOwned[account] > 0) {
            _tOwned[account] = tokenFromReflection(_rOwned[account]);
        }
        _isExcludedFromReflection[account] = true;
        _excluded.push(account);
    }

    function includeInReward(address account) external onlyOwner {
        require(_isExcludedFromReflection[account], "Account is not excluded");
        for (uint256 i = 0; i < _excluded.length; i++) {
            if (_excluded[i] == account) {
                _excluded[i] = _excluded[_excluded.length - 1];
                _tOwned[account] = 0;
                _isExcludedFromReflection[account] = false;
                _excluded.pop();
                break;
            }
        }
    }

    function excludeFromFee(address account) external onlyOwner {
        _isExcludedFromFee[account] = true;
    }

    function excludeMultipleAccountsFromMaxWallet(
        address[] calldata accounts,
        bool excluded
    ) public onlyOwner {
        for (uint256 i = 0; i < accounts.length; i++) {
            require(
                _isExcludedFromMaxWallet[accounts[i]] != excluded,
                "_isExcludedFromMaxWallet already set to that value for one wallet"
            );
            _isExcludedFromMaxWallet[accounts[i]] = excluded;
            emit ExcludeFromMaxWallet(accounts[i], excluded);
        }
    }

    function includeInFee(address account) external onlyOwner {
        _isExcludedFromFee[account] = false;
    }

    function isExcludedFromFee(address account) public view returns (bool) {
        return _isExcludedFromFee[account];
    }

    function isExcludedFromMaxWallet(address account)
        public
        view
        returns (bool)
    {
        return _isExcludedFromMaxWallet[account];
    }

    function setSwapAndLiquifyEnabled(bool _enabled) public onlyOwner {
        swapAndLiquifyEnabled = _enabled;
        emit SwapAndLiquifyEnabledUpdated(_enabled);
    }

    //  @dev receive ETH from UniswapV2Router when swapping
    receive() external payable {}

    function _reflectRfi(uint256 rRfi, uint256 tRfi) private {
        _rTotal -= rRfi;
        totFeesPaid.rfi += tRfi;
    }

    function _takeToSwap(uint256 rToSwap, uint256 tToSwap) private {
        _rOwned[address(this)] += rToSwap;
        if (_isExcludedFromReflection[address(this)]) _tOwned[address(this)] += tToSwap;
        totFeesPaid.toSwap += tToSwap;
    }

    function _getValues(uint256 tAmount, bool takeFee)
        private
        view
        returns (valuesFromGetValues memory to_return)
    {
        to_return = _getTValues(tAmount, takeFee);
        (
            to_return.rAmount,
            to_return.rTransferAmount,
            to_return.rRfi,
            to_return.rToSwap
        ) = _getRValues(to_return, tAmount, takeFee, _getRate());
        return to_return;
    }

    function _getTValues(uint256 tAmount, bool takeFee)
        private
        view
        returns (valuesFromGetValues memory s)
    {
        if (!takeFee) {
            s.tTransferAmount = tAmount;
            return s;
        }
        s.tRfi = (tAmount * appliedRates.rfi) / 100;
        s.tToSwap = (tAmount * appliedRates.toSwap) / 100;
        s.tTransferAmount = tAmount - s.tRfi - s.tToSwap;
        return s;
    }

    function _getRValues(
        valuesFromGetValues memory s,
        uint256 tAmount,
        bool takeFee,
        uint256 currentRate
    )
        private
        pure
        returns (
            uint256 rAmount,
            uint256 rTransferAmount,
            uint256 rRfi,
            uint256 rToSwap
        )
    {
        rAmount = tAmount * currentRate;

        if (!takeFee) {
            return (rAmount, rAmount, 0, 0);
        }

        rRfi = s.tRfi * currentRate;
        rToSwap = s.tToSwap * currentRate;
        rTransferAmount = rAmount - rRfi - rToSwap;
        return (rAmount, rTransferAmount, rRfi, rToSwap);
    }

    function _getRate() private view returns (uint256) {
        (uint256 rSupply, uint256 tSupply) = _getCurrentSupply();
        return rSupply / 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 -= _rOwned[_excluded[i]];
            tSupply -= _tOwned[_excluded[i]];
        }
        if (rSupply < _rTotal / _tTotal) return (_rTotal, _tTotal);
        return (rSupply, tSupply);
    }

    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) private {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");
        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    function _transfer(
        address from,
        address to,
        uint256 amount
    ) private {
        if (liqAddedBlockNumber == 0 && isAutomatedMarketMakerPair[to]) {
            liqAddedBlockNumber = block.number;
        }

        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");
        require(!isBot[from], "ERC20: address blacklisted (bot)");
        require(amount > 0, "Transfer amount must be greater than zero");
        require(
            amount <= balanceOf(from),
            "You are trying to transfer more than your balance"
        );
        bool takeFee = !(_isExcludedFromFee[from] || _isExcludedFromFee[to] || isAutomatedMarketMakerPair[from]);

        if (takeFee) {
            // Get the new total to see if it has spilled over the threshold
            //uint256 newTotalBuy = _amountBuy[from] + amount;
            uint256 newTotalSell = _amountSell[from] + amount;

            if (isAutomatedMarketMakerPair[from]) { //Buying
                if (block.number < liqAddedBlockNumber + blocksToWait) {
                    isBot[to] = true;
                    emit BlacklistedUser(to, true);
                }

                require(
                    amount <= maxTxAmountBuy,
                    "amount must be <= maxTxAmountBuy"
                );
            } else { //Selling
                if (newTotalSell > _whaleBuySellThreshold) {
                    appliedRates = whaleRates;
                } else {
                    appliedRates = buySellRates;
                }
                require(
                    amount <= maxTxAmountSell,
                    "amount must be <= maxTxAmountSell"
                );

                if (isAutomatedMarketMakerPair[to]) {
                    if (balanceOf(address(this)) >= numTokensSellToAddToLiquidity) {
                        distributeFees(balanceOf(address(this)));
                    }      
                }             
            }         
        }

        if ( ! isAutomatedMarketMakerPair[to]) {
            takeFee = false;
        }

        _tokenTransfer(from, to, amount, takeFee);
    }

    //this method is responsible for taking all fee, if takeFee is true
    function _tokenTransfer(
        address sender,
        address recipient,
        uint256 tAmount,
        bool takeFee
    ) private {
        valuesFromGetValues memory s = _getValues(tAmount, takeFee);

        if (_isExcludedFromReflection[sender]) {
            _tOwned[sender] -= tAmount;
        }
        if (_isExcludedFromReflection[recipient]) {
            _tOwned[recipient] += s.tTransferAmount;
        }

        _rOwned[sender] -= s.rAmount;
        _rOwned[recipient] += s.rTransferAmount;
        if (takeFee) {
            _reflectRfi(s.rRfi, s.tRfi);
            _takeToSwap(s.rToSwap, s.tToSwap);
            emit Transfer(sender, address(this), s.tToSwap);
        }
        require(
            _isExcludedFromMaxWallet[recipient] ||
                balanceOf(recipient) <= maxWalletAmount,
            "Recipient cannot hold more than maxWalletAmount"
        );
        emit Transfer(sender, recipient, s.tTransferAmount);
    }

    function distributeFees(uint256 contractTokenBalance) private {
        //Rewards
        uint256 devFee = contractTokenBalance * appliedRates.dev / 100;
        updateBalance(devAddress, devFee);

        //Project fund
        uint256 rewardFee = contractTokenBalance * appliedRates.reward / 100;   
        updateBalance(rewardsAddress, rewardFee);

        //Liquidity
        uint256 lpFee = contractTokenBalance * appliedRates.lp / 100; 
        updateBalance(lpAddress, lpFee);

        //Burn
        if (totalBurned() < maxBurn) {
            uint256 burnFee = contractTokenBalance * appliedRates.burn / 100;  
            updateBalance(burnAddress, burnFee);
        }
    }

    function swapAndLiquify(uint256 contractTokenBalance) private lockTheSwap {
        uint256 toAddLiquidityWith = contractTokenBalance * appliedRates.lp / 100;
        uint256 tokensToSwapToEth = toAddLiquidityWith / 2;
        uint256 tokenToAddToLiquidity = tokensToSwapToEth;

        uint256 initialBalance = address(this).balance;

        // swap tokens for ETH
        swapTokensForETH(tokensToSwapToEth);

        uint256 ethToAddToLiquidity = address(this).balance - initialBalance;

        // add liquidity
        addLiquidity(tokenToAddToLiquidity, ethToAddToLiquidity);
    }

    function swapTokensForETH(uint256 tokenAmount) private {
        // generate the pair path of token
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = UniswapV2Router.WETH();

        if (allowance(address(this), address(UniswapV2Router)) < tokenAmount) {
            _approve(address(this), address(UniswapV2Router), ~uint256(0));
        }

        // make the swap
        UniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            tokenAmount,
            0, // accept any amount of ETH
            path,
            address(this),
            block.timestamp
        );
    }

    function addLiquidity(uint256 tokenAmount, uint256 ETHAmount) private {
        // add the liquidity
        UniswapV2Router.addLiquidityETH{value: ETHAmount}(
            address(this),
            tokenAmount,
            0, // slippage is unavoidable
            0, // slippage is unavoidable
            burnAddress,
            block.timestamp
        );
        emit LiquidityAdded(tokenAmount, ETHAmount);
    }

    function setAutomatedMarketMakerPair(address _pair, bool value)
        external
        onlyOwner
    {
        require(
            isAutomatedMarketMakerPair[_pair] != value,
            "Automated market maker pair is already set to that value"
        );
        isAutomatedMarketMakerPair[_pair] = value;
        if (value) {
            _isExcludedFromMaxWallet[_pair] = true;
            emit ExcludeFromMaxWallet(_pair, value);
        }
        emit SetAutomatedMarketMakerPair(_pair, value);
    }

    function setBuyFees(
        uint8 _rfi,
        uint8 _reward,
        uint8 _dev,
        uint8 _lp
    ) external onlyOwner {
        buySellRates.rfi = _rfi;
        buySellRates.reward = _reward;
        buySellRates.dev = _dev;
        buySellRates.lp = _lp;
        buySellRates.toSwap = _reward + _dev + _lp;
    }

    function setWhaleFees(
        uint8 _rfi,
        uint8 _reward,
        uint8 _dev,
        uint8 _lp
    ) external onlyOwner {
        whaleRates.rfi = _rfi;
        whaleRates.reward = _reward;
        whaleRates.dev = _dev;
        whaleRates.lp = _lp;
        whaleRates.toSwap = _reward + _dev + _lp;
    }

    function setMaxTransactionAmount(
        uint256 _maxTxAmountBuyPct,
        uint256 _maxTxAmountSellPct
    ) external onlyOwner {
        maxTxAmountBuy = _tTotal / _maxTxAmountBuyPct; // 100 = 1%, 50 = 2% etc.
        maxTxAmountSell = _tTotal / _maxTxAmountSellPct; // 100 = 1%, 50 = 2% etc.
    }

    function setNumTokensSellToAddToLiq(uint256 amountTokens)
        external
        onlyOwner
    {
        numTokensSellToAddToLiquidity = amountTokens * 10**_decimals;
    }

    function setRewardsAddress(address payable _rewardsAddress) external onlyOwner {
        rewardsAddress = _rewardsAddress;
    }

    function setDevAddress(address payable _devAddress) external onlyOwner {
        devAddress = _devAddress;
    }

    function setBurnAddress(address payable _burnAddress) external onlyOwner {
        burnAddress = _burnAddress;
    }

    function setLiquidityAddress(address payable _lpAddress) external onlyOwner {
        lpAddress = _lpAddress;
    }

    function setWhaleBuySellThreshold(uint256 amount) external onlyOwner {
        _whaleBuySellThreshold = amount;
    }

    function manualSwapAndAddToLiq() external onlyOwner {
        swapAndLiquify(balanceOf(address(this)));
    }

    // Cannot BLACKLIST user manually, the only way to get into the Blacklist is to snipe, buy in block no.1. We give grace here if a genuine user can prove that they did not snipe in block 0 or 1.
    function unblacklistSniper(address botAddress) external onlyOwner {
        require(
            isBot[botAddress],
            "address provided is already not blacklisted"
        );
        isBot[botAddress] = false;
        emit BlacklistedUser(botAddress, false);
    }

    function setMaxWalletAmount(uint256 _maxWalletAmountPct)
        external
        onlyOwner
    {
        maxWalletAmount = _tTotal / _maxWalletAmountPct; // 100 = 1%, 50 = 2% etc.
        emit MaxWalletAmountUpdated(maxWalletAmount);
    }

    function excludeFromMaxWallet(address account, bool excluded)
        external
        onlyOwner
    {
        require(
            _isExcludedFromMaxWallet[account] != excluded,
            "_isExcludedFromMaxWallet already set to that value"
        );
        _isExcludedFromMaxWallet[account] = excluded;

        emit ExcludeFromMaxWallet(account, excluded);
    }

    function totalBurned() public view returns (uint256) {
		return balanceOf(burnAddress);
	}

    function updateBalance(address account, uint256 tokenAmount) private {
        if (_isExcludedFromReflection[account]) {
            _tOwned[account] += tokenAmount;
        } else {
            _rOwned[account] += tokenAmount;
        }

        emit Transfer(address(this), account, tokenAmount);            
    }
}
function excludeFromReward(address account) external onlyOwner {
        require(!_isExcludedFromReflection[account], "Account is already excluded");
        if (_rOwned[account] > 0) {
            _tOwned[account] = tokenFromReflection(_rOwned[account]);
        }
        _isExcludedFromReflection[account] = true;
        _excluded.push(account);
    }
//No current rfi - Tiered Rewarding Feature Applied at APP Launch