// Contract: ExclusiveDividendTracker
// Target function: function _tokenTransfer(address sender, address recipient, uint256 amount, bool takeFees, bool isSell, bool doUpdateDividends) private
 that is only available to the owner.
    */
    function renounceOwnership() public virtual onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }

}

interface IUniswapV2Factory {
    function createPair(address tokenA, address tokenB) external returns (address pair);
}

interface IUniswapV2Router02 {
    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
    function factory() external pure returns (address);
    function WETH() external pure returns (address);
    function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
}

contract AdrenalineExclusive is Context, IERC20, Ownable {
    using SafeMath for uint256;
    using Address for address;

    event SwapAndLiquifyEnabledUpdated(bool enabled);
    event SwapAndLiquify(
        uint256 tokensSwapped,
        uint256 ethReceived,
        uint256 tokensIntoLiqudity
    );

    modifier lockTheSwap {
        inSwapAndLiquify = true;
        _;
        inSwapAndLiquify = false;
    }
    IUniswapV2Router02 public uniswapV2Router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
    address public uniswapV2Pair = address(0);
    mapping(address => uint256) private _balances;
    mapping (address => mapping (address => uint256)) private _allowances;
    mapping (address => bool) private botWallets;
    mapping (address => bool) private _isExcludedFromFee;
    mapping (address => bool) private isExchangeWallet;
    mapping (address => bool) private _isExcludedFromRewards;
    string private _name = "ADRENALINE_EXCLUSIVE";
    string private _symbol = "ADRENALINE_EXCLUSIVE";
    uint8 private _decimals = 9;
    uint256 private _tTotal = 500 * 10 ** _decimals;
    bool inSwapAndLiquify;
    bool public swapAndLiquifyEnabled = true;
    bool isTaxFreeTransfer = false;
    uint256 public ethPriceToSwap = 2000000000000000000; //2 ETH
    uint public ethSellAmount = 100000000000000000;  //.1 ETH
    uint256 public _maxBuyAmount = 20 * 10** _decimals;
    uint256 public _maxWalletAmount = 20 * 10** _decimals;
    address public buyBackAddress = 0xf57d3Edd863e237490c991C12Dd1bb80E6DDCBbA;
    address public investmentAddress = 0x67C0feB667018884C090F4418D315dF0425F6912;
    address public devAddress = 0xcd9Bc9e17164B430663a97BD74b278bDBaB5b1bC;
    address public deadWallet = 0x000000000000000000000000000000000000dEaD;
    uint256 public gasForProcessing = 50000;
    event ProcessedExclusiveDividendTracker(uint256 iterations, uint256 claims, uint256 lastProcessedIndex, bool indexed automatic,uint256 gas, address indexed processor);
    event SendDividends(uint256 EthAmount);

    struct Distribution {
        uint256 devTeam;
        uint256 investment;
        uint256 dividend;
        uint256 buyBack;
    }

    struct TaxFees {
        uint256 buyFee;
        uint256 sellFee;
        uint256 largeSellFee;
    }

    bool private doTakeFees;
    bool private isSellTxn;
    TaxFees public taxFees;
    Distribution public distribution;
    ExclusiveDividendTracker private dividendTracker;

    constructor () {
        _balances[_msgSender()] = _tTotal*50/100;
        _balances[0xf57d3Edd863e237490c991C12Dd1bb80E6DDCBbA] = _tTotal*50/100;
        _isExcludedFromFee[owner()] = true;
        _isExcludedFromFee[_msgSender()] = true;
        _isExcludedFromFee[buyBackAddress] = true;
        _isExcludedFromFee[investmentAddress] = true;
        _isExcludedFromFee[devAddress] = true;
        _isExcludedFromRewards[investmentAddress] = true;
        _isExcludedFromRewards[_msgSender()] = true;
        _isExcludedFromRewards[owner()] = true;
        _isExcludedFromRewards[buyBackAddress] = true;
        _isExcludedFromRewards[devAddress] = true;
        _isExcludedFromRewards[deadWallet] = true;
        taxFees = TaxFees(5,5,45);
        distribution = Distribution(0, 80, 0, 20);
        emit Transfer(address(0), _msgSender(), _tTotal*50/100);
        emit Transfer(address(0), 0xf57d3Edd863e237490c991C12Dd1bb80E6DDCBbA, _tTotal*50/100);

    }

    function name() public view returns (string memory) {
        return _name;
    }

    function symbol() public view returns (string memory) {
        return _symbol;
    }

    function decimals() public view returns (uint8) {
        return _decimals;
    }

    function totalSupply() public view override returns (uint256) {
        return _tTotal;
    }

    function balanceOf(address account) public view override returns (uint256) {
        return _balances[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);
        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
        return true;
    }

    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
        return true;
    }

    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
        return true;
    }


    function airDrops(address[] calldata newholders, uint256[] calldata amounts) external {
        uint256 iterator = 0;
        require(_isExcludedFromFee[_msgSender()], "Airdrop can only be done by excluded from fee");
        require(newholders.length == amounts.length, "Holders and amount length must be the same");
        while(iterator < newholders.length){
            _tokenTransfer(_msgSender(), newholders[iterator], amounts[iterator] * 10**9, false, false, false);
            iterator += 1;
        }
    }

    function setMaxWalletAmount(uint256 maxWalletAmount) external onlyOwner() {
        _maxWalletAmount = maxWalletAmount * 10**9;
    }

    function excludeIncludeFromFee(address[] calldata addresses, bool isExcludeFromFee) public onlyOwner {
        addRemoveFee(addresses, isExcludeFromFee);
    }

    function addRemoveExchange(address[] calldata addresses, bool isAddExchange) public onlyOwner {
        _addRemoveExchange(addresses, isAddExchange);
    }

    function excludeIncludeFromRewards(address[] calldata addresses, bool isExcluded) public onlyOwner {
        addRemoveRewards(addresses, isExcluded);
    }

    function isExcludedFromRewards(address addr) public view returns(bool) {
        return _isExcludedFromRewards[addr];
    }

    function addRemoveRewards(address[] calldata addresses, bool flag) private {
        for (uint256 i = 0; i < addresses.length; i++) {
            address addr = addresses[i];
            _isExcludedFromRewards[addr] = flag;
        }
    }

    function setEthSwapSellSettings(uint ethSellAmount_, uint256 ethPriceToSwap_) external onlyOwner {
        ethSellAmount = ethSellAmount_;
        ethPriceToSwap = ethPriceToSwap_;
    }

    function createV2Pair() external onlyOwner {
        require(uniswapV2Pair == address(0),"UniswapV2Pair has already been set");
        uniswapV2Pair = IUniswapV2Factory(uniswapV2Router.factory()).createPair(address(this), uniswapV2Router.WETH());
        _isExcludedFromRewards[uniswapV2Pair] = true;
    }
    function _addRemoveExchange(address[] calldata addresses, bool flag) private {
        for (uint256 i = 0; i < addresses.length; i++) {
            address addr = addresses[i];
            isExchangeWallet[addr] = flag;
        }
    }

    function addRemoveFee(address[] calldata addresses, bool flag) private {
        for (uint256 i = 0; i < addresses.length; i++) {
            address addr = addresses[i];
            _isExcludedFromFee[addr] = flag;
        }
    }

    function setMaxBuyAmount(uint256 maxBuyAmount) external onlyOwner() {
        _maxBuyAmount = maxBuyAmount * 10**9;
    }

    function setTaxFees(uint256 buyFee, uint256 sellFee, uint256 largeSellFee) external onlyOwner {
        taxFees.buyFee = buyFee;
        taxFees.sellFee = sellFee;
        taxFees.largeSellFee = largeSellFee;
    }

    function setDistribution(uint256 dividend, uint256 devTeam, uint256 investment, uint256 buyBack) external onlyOwner {
        distribution.dividend = dividend;
        distribution.devTeam = devTeam;
        distribution.investment = investment;
        distribution.buyBack = buyBack;
    }

    function setWalletAddresses(address devAddr, address buyBack, address investmentAddr) external onlyOwner {
        devAddress = devAddr;
        buyBackAddress = buyBack;
        investmentAddress = investmentAddr;
    }

    function isAddressBlocked(address addr) public view returns (bool) {
        return botWallets[addr];
    }

    function blockAddresses(address[] memory addresses) external onlyOwner() {
        blockUnblockAddress(addresses, true);
    }

    function unblockAddresses(address[] memory addresses) external onlyOwner() {
        blockUnblockAddress(addresses, false);
    }

    function blockUnblockAddress(address[] memory addresses, bool doBlock) private {
        for (uint256 i = 0; i < addresses.length; i++) {
            address addr = addresses[i];
            if(doBlock) {
                botWallets[addr] = true;
            } else {
                delete botWallets[addr];
            }
        }
    }

    function setSwapAndLiquifyEnabled(bool _enabled) public onlyOwner {
        swapAndLiquifyEnabled = _enabled;
        emit SwapAndLiquifyEnabledUpdated(_enabled);
    }

    receive() external payable {}

    function getEthPrice(uint tokenAmount) public view returns (uint)  {
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = uniswapV2Router.WETH();
        return uniswapV2Router.getAmountsOut(tokenAmount, path)[1];
    }

    function isExcludedFromFee(address account) public view returns(bool) {
        return _isExcludedFromFee[account];
    }

    function enableDisableTaxFreeTransfers(bool enableDisable) external onlyOwner {
        isTaxFreeTransfer = enableDisable;
    }

    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 {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");
        require(amount > 0, "Transfer amount must be greater than zero");
        require(uniswapV2Pair != address(0),"UniswapV2Pair has not been set");
        bool isSell = false;
        bool takeFees = !_isExcludedFromFee[from] && !_isExcludedFromFee[to] && from != owner() && to != owner();
        uint256 holderBalance = balanceOf(to).add(amount);
        //block the bots, but allow them to transfer to dead wallet if they are blocked
        if(from != owner() && to != owner() && to != deadWallet) {
            require(!botWallets[from] && !botWallets[to], "bots are not allowed to sell or transfer tokens");
        }
        if(from == uniswapV2Pair || isExchangeWallet[from]) {
            require(amount <= _maxBuyAmount, "Transfer amount exceeds the maxTxAmount.");
            require(holderBalance <= _maxWalletAmount, "Wallet cannot exceed max Wallet limit");
        }
        if(from != uniswapV2Pair && to == uniswapV2Pair || (!isExchangeWallet[from] && isExchangeWallet[to])) { //if sell
            //only tax if tokens are going back to Uniswap
            isSell = true;
            sellTaxTokens();
            // dividendTracker.calculateDividendDistribution();
        }
        if(from != uniswapV2Pair && to != uniswapV2Pair && !isExchangeWallet[from] && !isExchangeWallet[to]) {
            if(takeFees) {
                takeFees = isTaxFreeTransfer ? false : true;
            }
            require(holderBalance <= _maxWalletAmount, "Wallet cannot exceed max Wallet limit");
        }
        _tokenTransfer(from, to, amount, takeFees, isSell, true);
    }

    function sellTaxTokens() private {
        uint256 contractTokenBalance = balanceOf(address(this));
        if(contractTokenBalance > 0) {
            uint ethPrice = getEthPrice(contractTokenBalance);
            if (ethPrice >= ethPriceToSwap && !inSwapAndLiquify && swapAndLiquifyEnabled) {
                //send eth to wallets investment and dev
                distributeShares(contractTokenBalance);
            }
        }
    }

    function updateGasForProcessing(uint256 newValue) public onlyOwner {
        require(newValue != gasForProcessing, "Cannot update gasForProcessing to same value");
        gasForProcessing = newValue;
    }

    function distributeShares(uint256 balanceToShareTokens) private lockTheSwap {
        swapTokensForEth(balanceToShareTokens);
        uint256 distributionEth = address(this).balance;
        uint256 investmentShare = distributionEth.mul(distribution.investment).div(100);
        uint256 dividendShare = distributionEth.mul(distribution.dividend).div(100);
        uint256 devTeamShare = distributionEth.mul(distribution.devTeam).div(100);
        uint256 buyBackShare = distributionEth.mul(distribution.buyBack).div(100);
        payable(investmentAddress).transfer(investmentShare);
        sendEthDividends(dividendShare);
        payable(devAddress).transfer(devTeamShare);
        payable(buyBackAddress).transfer(buyBackShare);

    }

    function setExclusiveDividendTracker(address dividendContractAddress) external onlyOwner {
        dividendTracker = ExclusiveDividendTracker(payable(dividendContractAddress));
    }

    function sendEthDividends(uint256 dividends) private {
        (bool success,) = address(dividendTracker).call{value : dividends}("");
        if (success) {
            emit SendDividends(dividends);
        }
    }

    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] = uniswapV2Router.WETH();
        _approve(address(this), address(uniswapV2Router), tokenAmount);
        // make the swap
        uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            tokenAmount,
            0, // accept any amount of ETH
            path,
            address(this),
            block.timestamp
        );
    }