// Contract: SpaceApe
// Target function: function excludeFromRewards(address _excludeRewardsAddr) public onlyOwner
idendPerShare
            .mul(value)
            .toInt256Safe();
        magnifiedDividendCorrections[from] = magnifiedDividendCorrections[from]
            .add(_magCorrection);
        magnifiedDividendCorrections[to] = magnifiedDividendCorrections[to].sub(
            _magCorrection
        );
    }

    /// @dev Internal function that mints tokens to an account.
    /// Update magnifiedDividendCorrections to keep dividends unchanged.
    /// @param account The account that will receive the created tokens.
    /// @param value The amount that will be created.
    function _mint(address account, uint256 value) internal override {
        super._mint(account, value);

        magnifiedDividendCorrections[account] = magnifiedDividendCorrections[
            account
        ].sub((magnifiedDividendPerShare.mul(value)).toInt256Safe());
    }

    /// @dev Internal function that burns an amount of the token of a given account.
    /// Update magnifiedDividendCorrections to keep dividends unchanged.
    /// @param account The account whose tokens will be burnt.
    /// @param value The amount that will be burnt.
    function _burn(address account, uint256 value) internal override {
        super._burn(account, value);

        magnifiedDividendCorrections[account] = magnifiedDividendCorrections[
            account
        ].add((magnifiedDividendPerShare.mul(value)).toInt256Safe());
    }

    function _setBalance(address account, uint256 newBalance) internal {
        uint256 currentBalance = balanceOf(account);

        if (newBalance > currentBalance) {
            uint256 mintAmount = newBalance.sub(currentBalance);
            _mint(account, mintAmount);
        } else if (newBalance < currentBalance) {
            uint256 burnAmount = currentBalance.sub(newBalance);
            _burn(account, burnAmount);
        }
    }
}

// Token Dividend Tracker contract to track dividends of SPACEAPE token holders
contract TokenDividendTracker is DividendPayingToken, Ownable {
    using SafeMath for uint256;
    using SafeMathUint for uint256;
    using SafeMathInt for int256;
    using IterableMapping for IterableMapping.Map;

    IterableMapping.Map private tokenHoldersMap;
    uint256 public lastProcessedIndex;

    mapping(address => bool) public excludedFromDividends;

    mapping(address => uint256) public lastClaimTimes;

    uint256 public claimWait;
    uint256 public immutable minimumTokenBalanceForDividends;

    event ExcludeFromDividends(address indexed account);
    event IncludeInDividends(address indexed account);

    event ClaimWaitUpdated(uint256 indexed newValue, uint256 indexed oldValue);

    event Claim(
        address indexed account,
        uint256 amount,
        bool indexed automatic
    );

    constructor(
        string memory _name,
        string memory _symbol,
        address rewardToken
    ) DividendPayingToken(_name, _symbol, rewardToken) {
        claimWait = 3600;
        minimumTokenBalanceForDividends = 1 * (10**14); // must hold 0.0001%
    }

    function _transfer(
        address,
        address,
        uint256
    ) internal pure override {
        require(false, "SPACEAPE_Dividend_Tracker: No transfers allowed");
    }

    function withdrawDividend() public pure override {
        require(
            false,
            "SPACEAPE_Dividend_Tracker: withdrawDividend disabled. Use the 'claim' function on the main $APE contract."
        );
    }

    // A function that sets a 0 reward token balance for the given account and removes them from the tokenHoldersMap if they aren't already excluded
    function excludeFromDividends(address account) external onlyOwner {
        require(!excludedFromDividends[account], "Account is already excluded");
        excludedFromDividends[account] = true;

        _setBalance(account, 0);
        tokenHoldersMap.remove(account);

        emit ExcludeFromDividends(account);
    }

    // A function that sets a 0 reward token balance for the given account and adds them to the tokenHoldersMap if they aren't already included
    function includeInDividends(address account) external onlyOwner {
        require(excludedFromDividends[account], "Account is already included");
        excludedFromDividends[account] = false;

        _setBalance(account, 0);
        tokenHoldersMap.set(account, 0);

        emit IncludeInDividends(account);
    }

    // a function to check if the given account is excluded from dividends
    function isexcludeFromDividends(address account)
        external
        view
        returns (bool)
    {
        return excludedFromDividends[account];
    }

    // a function that updates the claimWait time, only allowing it to be within 1 and 24 hours
    function updateClaimWait(uint256 newClaimWait) external onlyOwner {
        require(
            newClaimWait >= 3600 && newClaimWait <= 86400,
            "SPACEAPE_Dividend_Tracker: claimWait must be updated to between 1 and 24 hours"
        );
        require(
            newClaimWait != claimWait,
            "SPACEAPE_Dividend_Tracker: Cannot update claimWait to same value"
        );
        emit ClaimWaitUpdated(newClaimWait, claimWait);
        claimWait = newClaimWait;
    }

    // a function that returns the token balance for the given account in the dividendtracker interface
    function dividendTokenBalanceOf(address account)
        public
        view
        returns (uint256)
    {
        return balanceOf(account);
    }

    // a function to return the last time an index was processed
    function getLastProcessedIndex() external view returns (uint256) {
        return lastProcessedIndex;
    }

    // a function to get the total number of token holders from the tokenHoldersMap
    function getNumberOfTokenHolders() external view returns (uint256) {
        return tokenHoldersMap.keys.length;
    }

    //  gets the given address details, allowing public view
    function getAccount(address _account)
        public
        view
        returns (
            address account,
            int256 index,
            int256 iterationsUntilProcessed,
            uint256 withdrawableDividends,
            uint256 totalDividends,
            uint256 lastClaimTime,
            uint256 nextClaimTime,
            uint256 secondsUntilAutoClaimAvailable
        )
    {
        account = _account;

        index = tokenHoldersMap.getIndexOfKey(account);

        iterationsUntilProcessed = -1;

        if (index >= 0) {
            if (uint256(index) > lastProcessedIndex) {
                iterationsUntilProcessed = index.sub(
                    int256(lastProcessedIndex)
                );
            } else {
                uint256 processesUntilEndOfArray = tokenHoldersMap.keys.length >
                    lastProcessedIndex
                    ? tokenHoldersMap.keys.length.sub(lastProcessedIndex)
                    : 0;

                iterationsUntilProcessed = index.add(
                    int256(processesUntilEndOfArray)
                );
            }
        }

        withdrawableDividends = withdrawableDividendOf(account);
        totalDividends = accumulativeDividendOf(account);

        lastClaimTime = lastClaimTimes[account];

        nextClaimTime = lastClaimTime > 0 ? lastClaimTime.add(claimWait) : 0;

        secondsUntilAutoClaimAvailable = nextClaimTime > block.timestamp
            ? nextClaimTime.sub(block.timestamp)
            : 0;
    }

    function getAccountAtIndex(uint256 index)
        public
        view
        returns (
            address,
            int256,
            int256,
            uint256,
            uint256,
            uint256,
            uint256,
            uint256
        )
    {
        if (index >= tokenHoldersMap.size()) {
            return (
                0x0000000000000000000000000000000000000000,
                -1,
                -1,
                0,
                0,
                0,
                0,
                0
            );
        }

        address account = tokenHoldersMap.getKeyAtIndex(index);

        return getAccount(account);
    }

    function canAutoClaim(uint256 lastClaimTime) private view returns (bool) {
        if (lastClaimTime > block.timestamp) {
            return false;
        }

        return block.timestamp.sub(lastClaimTime) >= claimWait;
    }

    function setBalance(address payable account, uint256 newBalance)
        external
        onlyOwner
    {
        if (excludedFromDividends[account]) {
            return;
        }

        if (newBalance >= minimumTokenBalanceForDividends) {
            _setBalance(account, newBalance);
            tokenHoldersMap.set(account, newBalance);
        } else {
            _setBalance(account, 0);
            tokenHoldersMap.remove(account);
        }

        processAccount(account, true);
    }

    function process(uint256 gas)
        public
        returns (
            uint256,
            uint256,
            uint256
        )
    {
        uint256 numberOfTokenHolders = tokenHoldersMap.keys.length;

        if (numberOfTokenHolders == 0) {
            return (0, 0, lastProcessedIndex);
        }

        uint256 _lastProcessedIndex = lastProcessedIndex;

        uint256 gasUsed = 0;

        uint256 gasLeft = gasleft();

        uint256 iterations = 0;
        uint256 claims = 0;

        while (gasUsed < gas && iterations < numberOfTokenHolders) {
            _lastProcessedIndex++;

            if (_lastProcessedIndex >= tokenHoldersMap.keys.length) {
                _lastProcessedIndex = 0;
            }

            address account = tokenHoldersMap.keys[_lastProcessedIndex];

            if (canAutoClaim(lastClaimTimes[account])) {
                if (processAccount(payable(account), true)) {
                    claims++;
                }
            }

            iterations++;

            uint256 newGasLeft = gasleft();

            if (gasLeft > newGasLeft) {
                gasUsed = gasUsed.add(gasLeft.sub(newGasLeft));
            }

            gasLeft = newGasLeft;
        }

        lastProcessedIndex = _lastProcessedIndex;

        return (iterations, claims, lastProcessedIndex);
    }

    function processAccount(address payable account, bool automatic)
        public
        onlyOwner
        returns (bool)
    {
        uint256 amount = _withdrawDividendOfUser(account);

        if (amount > 0) {
            lastClaimTimes[account] = block.timestamp;
            emit Claim(account, amount, automatic);
            return true;
        }

        return false;
    }
}

contract SpaceApe is ERC20, Ownable {
    using SafeMath for uint256;

    bool private swapping;

    TokenDividendTracker public dividendTracker;

    mapping(address => bool) private _isExcludedFromFees;
    mapping(address => bool) public _isExcludedMaxSellTransactionAmount;
    mapping(address => bool) private _isLaunch;

    // store addresses that a automatic market maker pairs. Any transfer *to* these addresses
    // could be subject to a maximum transfer amount
    mapping(address => bool) public automatedMarketMakerPairs;

    IUniswapV2Router02 public uniswapV2Router;
    address public uniswapV2Pair;

    address public REWARDToken = 0xF7Ecb2E5ddaD17506E62F51A442f725a26053fb2;

    address public treasury;
    address public ops1;
    address public ops2;
    address public burnWormhole = address(0xdead);
    address public liquidityAddr;

    uint256 public supply;

    uint256 public maxSellTransactionAmount;
    uint256 public maxWalletTotal;
    uint256 public maxWallet = 2;

    uint256 private feeUnits = 1000;
    uint256 public totalStandardFee;
    uint256 public tokenRewardPercent = 40;
    uint256 public liquidityPercent = 5;
    uint256 public opsMarketingPercent = 30;
    uint256 public burnPercent = 5;

    uint256 public totalAntiDumpFee;
    uint256 public antiDumpLiquidity = 5;
    uint256 public antiDumpMarketAndRewards = 10;
    uint256 public antiDumpBurn = 5;

    uint256 private treasuryDiv = 600;
    uint256 private opsDiv = 200;

    uint256 public maxSellTransaction = 1; // 1%
    uint256 private swapMultiplier = 10000; // 0.0005%

    bool public beforeTradingLaunched = true;

    uint256 public liquidityBalance;
    uint256 private burnBalance;
    uint256 public swapTokensAtAmount;

    uint256 public gasForProcessing = 300000;

    bool public directLiquidityInjectionEnabled = true;
    bool private swapTreasury = true;

    event UpdateDividendTracker(
        address indexed newAddress,
        address indexed oldAddress
    );
    event UpdateUniswapV2Router(
        address indexed newAddress,
        address indexed oldAddress
    );
    event liquidityAddrUpdated(
        address indexed newliquidityAddr,
        address indexed oldliquidityAddr
    );
    event SetBalance(address from, uint256 amount);
    event Log(bool swapping, bool fee);

    address public presaleAddress = address(0);

    uint256 public tradingEnabledTimestamp;

    event SetAutomatedMarketMakerPair(address indexed pair, bool indexed value);
    event ProcessedDividendTracker(
        uint256 iterations,
        uint256 claims,
        uint256 lastProcessedIndex,
        bool indexed automatic,
        uint256 gas,
        address indexed processor
    );

    constructor() ERC20("SpaceApe", "$APE") {
        dividendTracker = new TokenDividendTracker(
            "SPACEAPE_Dividend_Tracker",
            "SPACEAPE_Dividend_Tracker",
            REWARDToken
        );

        liquidityAddr = owner();

        IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(
            0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
        );
        address _uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory())
            .createPair(address(this), _uniswapV2Router.WETH());
        uniswapV2Pair = _uniswapV2Pair;
        excludeFromMaxSellTransaction(address(_uniswapV2Router), true);
        uniswapV2Router = _uniswapV2Router;
        excludeFromMaxSellTransaction(address(uniswapV2Pair), true);
        _setAutomatedMarketMakerPair(address(uniswapV2Pair), true);

        totalStandardFee = liquidityPercent + opsMarketingPercent + burnPercent + tokenRewardPercent;
        totalAntiDumpFee = antiDumpLiquidity + antiDumpMarketAndRewards + antiDumpBurn;

        uint256 totalSupply = 420690420 * (10**18);
        supply += totalSupply;

        maxSellTransactionAmount = (supply * maxSellTransaction) / 100; // 2%
        swapTokensAtAmount = (supply * 5) / swapMultiplier; // 0.0005% swap wallet;
        maxWalletTotal = (supply * maxWallet) / 100;

        _isExcludedFromFees[address(this)] = true;
        _isExcludedFromFees[liquidityAddr] = true;
        _isExcludedFromFees[treasury] = true;
        _isExcludedFromFees[burnWormhole] = true;

        _isLaunch[address(this)] = true;
        _isLaunch[liquidityAddr] = true;
        _isLaunch[_uniswapV2Pair] = true;
        _isLaunch[0x0000000000000000000000000000000000000000] = true;

        // exclude from receiving dividends
        dividendTracker.excludeFromDividends(address(this));
        dividendTracker.excludeFromDividends(address(dividendTracker));
        dividendTracker.excludeFromDividends(liquidityAddr);
        dividendTracker.excludeFromDividends(burnWormhole);
        dividendTracker.excludeFromDividends(address(_uniswapV2Router));

        // supply to mint
        _mint(owner(), totalSupply);
        tradingEnabledTimestamp = 0;
    }

    receive() external payable {}

    // add dividendtracker update function for future upgradeability & dApps
    function updateDividendTracker(address newAddress) public onlyOwner {
        require(
            newAddress != address(dividendTracker),
            "SPACEAPE: The dividend tracker already has that address"
        );

        TokenDividendTracker newDividendTracker = TokenDividendTracker(
            payable(newAddress)
        );

        require(
            newDividendTracker.owner() == address(this),
            "SPACEAPE: The new dividend tracker must be owned by the $APE token contract"
        );

        newDividendTracker.excludeFromDividends(address(newDividendTracker));
        newDividendTracker.excludeFromDividends(address(this));
        newDividendTracker.excludeFromDividends(owner());
        newDividendTracker.excludeFromDividends(address(uniswapV2Router));

        emit UpdateDividendTracker(newAddress, address(dividendTracker));

        dividendTracker = newDividendTracker;
    }

    // add uniswap router upgrade function in case of pancakeswap update
    function updateUniswapV2Router(address newAddress) public onlyOwner {
        require(
            newAddress != address(uniswapV2Router),
            "SPACEAPE: The router already has that address"
        );
        emit UpdateUniswapV2Router(newAddress, address(uniswapV2Router));
        uniswapV2Router = IUniswapV2Router02(newAddress);
    }