// Contract: BankofBitcoin
// Target function: function excludeDividends(address account) external onlyOwner
 The address of a token holder.
    /// @return The amount of dividend in wei that `_owner` has earned in total.
    function accumulativeDividendOf(
        address _owner,
        address _rewardToken
    ) public view override returns (uint256) {
        return
            magnifiedDividendPerShare[_rewardToken]
                .mul(holderBalance[_owner])
                .toInt256Safe()
                .add(magnifiedDividendCorrections[_rewardToken][_owner])
                .toUint256Safe() / magnitude;
    }

    /// @dev Internal function that increases 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 _increase(address account, uint256 value) internal {
        for (uint256 i; i < rewardTokens.length; i++) {
            magnifiedDividendCorrections[rewardTokens[i]][
                account
            ] = magnifiedDividendCorrections[rewardTokens[i]][account].sub(
                (magnifiedDividendPerShare[rewardTokens[i]].mul(value))
                    .toInt256Safe()
            );
        }
    }

    /// @dev Internal function that reduces 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 _reduce(address account, uint256 value) internal {
        for (uint256 i; i < rewardTokens.length; i++) {
            magnifiedDividendCorrections[rewardTokens[i]][
                account
            ] = magnifiedDividendCorrections[rewardTokens[i]][account].add(
                (magnifiedDividendPerShare[rewardTokens[i]].mul(value))
                    .toInt256Safe()
            );
        }
    }

    function _setBalance(address account, uint256 newBalance) internal {
        uint256 currentBalance = holderBalance[account];
        holderBalance[account] = newBalance;
        if (newBalance > currentBalance) {
            uint256 increaseAmount = newBalance.sub(currentBalance);
            _increase(account, increaseAmount);
            totalBalance += increaseAmount;
        } else if (newBalance < currentBalance) {
            uint256 reduceAmount = currentBalance.sub(newBalance);
            _reduce(account, reduceAmount);
            totalBalance -= reduceAmount;
        }
    }
}

contract DividendTracker is DividendPayingToken {
    using SafeMath for uint256;
    using SafeMathInt for int256;

    struct Map {
        address[] keys;
        mapping(address => uint) values;
        mapping(address => uint) indexOf;
        mapping(address => bool) inserted;
    }

    function get(address key) private view returns (uint) {
        return tokenHoldersMap.values[key];
    }

    function getIndexOfKey(address key) private view returns (int) {
        if (!tokenHoldersMap.inserted[key]) {
            return -1;
        }
        return int(tokenHoldersMap.indexOf[key]);
    }

    function getKeyAtIndex(uint index) private view returns (address) {
        return tokenHoldersMap.keys[index];
    }

    function size() private view returns (uint) {
        return tokenHoldersMap.keys.length;
    }

    function set(address key, uint val) private {
        if (tokenHoldersMap.inserted[key]) {
            tokenHoldersMap.values[key] = val;
        } else {
            tokenHoldersMap.inserted[key] = true;
            tokenHoldersMap.values[key] = val;
            tokenHoldersMap.indexOf[key] = tokenHoldersMap.keys.length;
            tokenHoldersMap.keys.push(key);
        }
    }

    function remove(address key) private {
        if (!tokenHoldersMap.inserted[key]) {
            return;
        }

        delete tokenHoldersMap.inserted[key];
        delete tokenHoldersMap.values[key];

        uint index = tokenHoldersMap.indexOf[key];
        uint lastIndex = tokenHoldersMap.keys.length - 1;
        address lastKey = tokenHoldersMap.keys[lastIndex];

        tokenHoldersMap.indexOf[lastKey] = index;
        delete tokenHoldersMap.indexOf[key];

        tokenHoldersMap.keys[index] = lastKey;
        tokenHoldersMap.keys.pop();
    }

    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() {
        claimWait = 1200;
        minimumTokenBalanceForDividends = 10500 * (10 ** 18);
    }

    function excludeFromDividends(address account) external onlyOwner {
        excludedFromDividends[account] = true;

        _setBalance(account, 0);
        remove(account);

        emit ExcludeFromDividends(account);
    }

    function includeInDividends(address account) external onlyOwner {
        require(excludedFromDividends[account]);
        excludedFromDividends[account] = false;

        emit IncludeInDividends(account);
    }

    function updateClaimWait(uint256 newClaimWait) external onlyOwner {
        require(
            newClaimWait >= 1200 && newClaimWait <= 86400,
            "Dividend_Tracker: claimWait must be updated to between 1 and 24 hours"
        );
        require(
            newClaimWait != claimWait,
            "Dividend_Tracker: Cannot update claimWait to same value"
        );
        emit ClaimWaitUpdated(newClaimWait, claimWait);
        claimWait = newClaimWait;
    }

    function getLastProcessedIndex() external view returns (uint256) {
        return lastProcessedIndex;
    }

    function getNumberOfTokenHolders() external view returns (uint256) {
        return tokenHoldersMap.keys.length;
    }

    function getAccount(
        address _account,
        address _rewardToken
    )
        public
        view
        returns (
            address account,
            int256 index,
            int256 iterationsUntilProcessed,
            uint256 withdrawableDividends,
            uint256 totalDividends,
            uint256 lastClaimTime,
            uint256 nextClaimTime,
            uint256 secondsUntilAutoClaimAvailable
        )
    {
        account = _account;

        index = 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, _rewardToken);
        totalDividends = accumulativeDividendOf(account, _rewardToken);

        lastClaimTime = lastClaimTimes[account];

        nextClaimTime = lastClaimTime > 0 ? lastClaimTime.add(claimWait) : 0;

        secondsUntilAutoClaimAvailable = nextClaimTime > block.timestamp
            ? nextClaimTime.sub(block.timestamp)
            : 0;
    }

    function getAccountAtIndex(
        uint256 index,
        address _rewardToken
    )
        external
        view
        returns (
            address,
            int256,
            int256,
            uint256,
            uint256,
            uint256,
            uint256,
            uint256
        )
    {
        if (index >= size()) {
            return (
                0x0000000000000000000000000000000000000000,
                -1,
                -1,
                0,
                0,
                0,
                0,
                0
            );
        }

        address account = getKeyAtIndex(index);

        return getAccount(account, _rewardToken);
    }

    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);
            set(account, newBalance);
        } else {
            _setBalance(account, 0);
            remove(account);
        }

        processAccount(account, true);
    }

    function process(uint256 gas) external 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;
        bool paid;
        for (uint256 i; i < rewardTokens.length; i++) {
            amount = _withdrawDividendOfUser(account, rewardTokens[i]);
            if (amount > 0) {
                lastClaimTimes[account] = block.timestamp;
                emit Claim(account, amount, automatic);
                paid = true;
            }
        }
        return paid;
    }
}

contract BankofBitcoin is ERC20, Ownable {
    using SafeMath for uint256;

    IUniswapV2Router02 private immutable uniswapV2Router;
    address private immutable uniswapV2Pair;

    bool private swapping;

    DividendTracker public dividendTracker;

    address private operationsWallet;

    uint256 private swapTokensAtAmount;
    uint256 private maxTxn;

    uint256 liquidityActiveBlock = 0; // 0 means liquidity is not active yet
    uint256 tradingActiveBlock = 0; // 0 means trading is not active
    uint256 earlyBuyPenaltyEnd; // determines when snipers/bots can sell without extra penalty

    bool private limitsInEffect = true;
    bool public tradingActive = false;
    bool private swapEnabled = false;

    // Anti-bot and anti-whale mappings and variables
    mapping(address => uint256) private _holderLastTransferTimestamp; // to hold last Transfers temporarily during launch
    bool private transferDelayEnabled = true;

    uint256 private constant feeDivisor = 1000;

    uint256 private totalSellFees;
    uint256 private rewardsSellFee;
    uint256 private operationsSellFee;
    uint256 private liquiditySellFee;

    uint256 private totalBuyFees;
    uint256 private rewardsBuyFee;
    uint256 private operationsBuyFee;
    uint256 private liquidityBuyFee;

    uint256 private tokensForRewards;
    uint256 private tokensForOperations;
    uint256 private tokensForLiquidity;

    uint256 private gasForProcessing = 0;
    /******************/

    bool public whitelistMode = true;
    mapping(address => bool) public whitelist;

    // exlcude from fees and max transaction amount
    mapping(address => bool) private _isExcludedFromFees;

    mapping(address => bool) public _isExcludedMaxTransactionAmount;

    // 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;

    event ExcludeFromFees(address indexed account, bool isExcluded);
    event ExcludeMultipleAccountsFromFees(address[] accounts, bool isExcluded);
    event ExcludedMaxTransactionAmount(
        address indexed account,
        bool isExcluded
    );

    event SetAutomatedMarketMakerPair(address indexed pair, bool indexed value);

    event OperationsWalletUpdated(
        address indexed newWallet,
        address indexed oldWallet
    );

    event DevWalletUpdated(
        address indexed newWallet,
        address indexed oldWallet
    );

    event GasForProcessingUpdated(
        uint256 indexed newValue,
        uint256 indexed oldValue
    );

    event SwapAndLiquify(
        uint256 tokensSwapped,
        uint256 ethReceived,
        uint256 tokensIntoLiqudity
    );

    event SendDividends(uint256 tokensSwapped, uint256 amount);

    event ProcessedDividendTracker(
        uint256 iterations,
        uint256 claims,
        uint256 lastProcessedIndex,
        bool indexed automatic,
        uint256 gas,
        address indexed processor
    );

    constructor() ERC20("Bank of Bitcoin", "BOB") {
        uint256 totalSupply = 21000000 * 1e18;

        swapTokensAtAmount = (totalSupply * 1) / 10000; // 0.01% swap tokens amount
        maxTxn = (totalSupply * 1) / 100; // 1% Max wallet

        rewardsBuyFee = 0;
        operationsBuyFee = 0;
        liquidityBuyFee = 0;
        totalBuyFees = rewardsBuyFee + operationsBuyFee + liquidityBuyFee;

        rewardsSellFee = 10;
        operationsSellFee = 490;
        liquiditySellFee = 0;
        totalSellFees = rewardsSellFee + operationsSellFee + liquiditySellFee;

        dividendTracker = new DividendTracker();

        operationsWallet = address(0x7E1eCe08A2971cf10eD0d89b5f0Dd1A5bdBe6776); // set as operations wallet

        IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(
            0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
        ); //0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D

        // Create a uniswap pair for this new token
        address _uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory())
            .createPair(address(this), _uniswapV2Router.WETH());

        uniswapV2Router = _uniswapV2Router;
        uniswapV2Pair = _uniswapV2Pair;

        _setAutomatedMarketMakerPair(_uniswapV2Pair, true);

        // exclude from receiving dividends
        dividendTracker.excludeFromDividends(address(dividendTracker));
        dividendTracker.excludeFromDividends(address(this));
        dividendTracker.excludeFromDividends(owner());
        dividendTracker.excludeFromDividends(address(_uniswapV2Router));
        dividendTracker.excludeFromDividends(address(0xdead));

        // exclude from paying fees or having max transaction amount
        excludeFromFees(owner(), true);
        excludeFromFees(address(this), true);
        excludeFromFees(address(0xdead), true);
        excludeFromMaxTransaction(owner(), true);
        excludeFromMaxTransaction(address(this), true);
        excludeFromMaxTransaction(address(dividendTracker), true);
        excludeFromMaxTransaction(address(_uniswapV2Router), true);
        excludeFromMaxTransaction(address(0xdead), true);

        whitelist[owner()] = true;

        _createInitialSupply(address(owner()), totalSupply);
    }

    receive() external payable {}

    function disableWhiteList() external onlyOwner {
        whitelistMode = false;
        rewardsBuyFee = 10;
        operationsBuyFee = 290;
        liquidityBuyFee = 0;
        totalBuyFees = rewardsBuyFee + operationsBuyFee + liquidityBuyFee;
    }

    function addManyWhitelist(
        address[] calldata _addresses,
        bool state
    ) external onlyOwner {
        for (uint256 i = 0; i < _addresses.length; i++) {
            whitelist[_addresses[i]] = state;
        }
    }

    // only use if conducting a presale
    function addDapp(address _presaleAddress) external onlyOwner {
        excludeFromFees(_presaleAddress, true);
        dividendTracker.excludeFromDividends(_presaleAddress);
        excludeFromMaxTransaction(_presaleAddress, true);
    }

    // disable Transfer delay - cannot be reenabled
    function disbleTransferDelay() external onlyOwner returns (bool) {
        transferDelayEnabled = false;
        return true;
    }