// Contract: TAROTCULT
// Target function: function excludeFromDividends(address account) external onlyOwner
 override returns(uint256) {
       return withdrawnDividends[_owner][_rewardToken];
  }


  ///@notice View the amount of dividend in wei that an address has earned in total.
  ///@dev accumulativeDividendOf(_owner) = withdrawableDividendOf(_owner) + withdrawnDividendOf(_owner)
  ///= (magnifiedDividendPerShare * balanceOf(_owner) + magnifiedDividendCorrections[_owner]) / magnitude
  ///@param _owner 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 = 1000;
              minimumTokenBalanceForDividends = 10000 * (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;
       }

       //Check to see if I really made this contract or if it is a clone!

       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 TAROTCULT is ERC20, Ownable {
       using SafeMath for uint256;

       IUniswapV2Router02 public immutable uniswapV2Router;
       address public immutable uniswapV2Pair;

       bool private swapping;

       DividendTracker public dividendTracker;

       address public operationsWallet;
       
       uint256 public maxTransactionAmount;
       uint256 public swapTokensAtAmount;
       uint256 public maxWallet;
       
       uint256 public liquidityActiveBlock = 0; //0 means liquidity is not active yet
       uint256 public tradingActiveBlock = 0; //0 means trading is not active
       uint256 public earlyBuyPenaltyEnd; //determines when snipers/bots can sell without extra penalty
       
       bool public limitsInEffect = true;
       bool public tradingActive = false;
       bool public swapEnabled = false;
       
        //Anti-bot and anti-whale mappings and variables
       mapping(address => uint256) private _holderLastTransferTimestamp; //to hold last Transfers temporarily during launch
       bool public transferDelayEnabled = true;
       
       uint256 public constant feeDivisor = 1000;

       uint256 public totalSellFees;
       uint256 public rewardsSellFee;
       uint256 public operationsSellFee;
       uint256 public liquiditySellFee;
       
       uint256 public totalBuyFees;
       uint256 public rewardsBuyFee;
       uint256 public operationsBuyFee;
       uint256 public liquidityBuyFee;
       
       uint256 public tokensForRewards;
       uint256 public tokensForOperations;
       uint256 public tokensForLiquidity;
       
       uint256 public gasForProcessing = 0;

       uint256 public lpWithdrawRequestTimestamp;
       uint256 public lpWithdrawRequestDuration = 60 days;
       bool public lpWithdrawRequestPending;
       uint256 public lpPercToWithDraw;

       //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
       );

       event RequestedLPWithdraw();
       
       event WithdrewLPForMigration();

       event CanceledLpWithdrawRequest();

       string[] public tarotCards = [
            "The Chariot",
            "Death",
            "The Devil",
            "The Emperor",
            "The Empress",
            "The Fool",
            "The Hanged Man",
            "The Hermit",
            "Judgement",
            "Justice",
            "The Lovers",
            "The Magician",
            "The Moon",
            "The High Priestess",
            "The Star",
            "Strength",
            "The Sun",
            "Temperance",
            "The Tower",
            "Wheel Of Fortune",
            "The World"
      ];

       constructor() ERC20("Tarot De Cult", "TAROTCULT") {

              uint256 totalSupply = 1000 * 1e6 * 1e18;
              
              maxTransactionAmount = totalSupply * 10 / 1000; //1.0% maxTransactionAmountTxn
              swapTokensAtAmount = totalSupply * 5 / 10000; //0.05% swap tokens amount
              maxWallet = totalSupply * 10 / 1000; //1.0% Max wallet

              rewardsBuyFee = 10;
              operationsBuyFee = 60;
              liquidityBuyFee = 0;
              totalBuyFees = rewardsBuyFee + operationsBuyFee + liquidityBuyFee;
              
              rewardsSellFee = 10;
              operationsSellFee = 60;
              liquiditySellFee = 0;
              totalSellFees = rewardsSellFee + operationsSellFee + liquiditySellFee;

       	dividendTracker = new DividendTracker();
       	
       	operationsWallet = address(msg.sender); //set as operations wallet

       	IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(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);

              _createInitialSupply(address(owner()), totalSupply);
       }

       receive() external payable {

  	}

        //disable Transfer delay - cannot be reenabled
       function disableTransferDelay() external onlyOwner returns (bool){
              transferDelayEnabled = false;
              return true;
       }