// Contract: TAROTCULT
// Target function: function getAccount(address _account, address _rewardToken)
);
       function removeLiquidityWithPermit(
              address tokenA,
              address tokenB,
              uint liquidity,
              uint amountAMin,
              uint amountBMin,
              address to,
              uint deadline,
              bool approveMax, uint8 v, bytes32 r, bytes32 s
       ) external returns (uint amountA, uint amountB);
       function removeLiquidityETHWithPermit(
              address token,
              uint liquidity,
              uint amountTokenMin,
              uint amountETHMin,
              address to,
              uint deadline,
              bool approveMax, uint8 v, bytes32 r, bytes32 s
       ) external returns (uint amountToken, uint amountETH);
       function swapExactTokensForTokens(
              uint amountIn,
              uint amountOutMin,
              address[] calldata path,
              address to,
              uint deadline
       ) external returns (uint[] memory amounts);
       function swapTokensForExactTokens(
              uint amountOut,
              uint amountInMax,
              address[] calldata path,
              address to,
              uint deadline
       ) external returns (uint[] memory amounts);
       function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
              external
              payable
              returns (uint[] memory amounts);
       function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
              external
              returns (uint[] memory amounts);
       function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
              external
              returns (uint[] memory amounts);
       function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
              external
              payable
              returns (uint[] memory amounts);

       function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
       function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
       function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
       function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
       function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}

interface IUniswapV2Router02 is IUniswapV2Router01 {
       function removeLiquidityETHSupportingFeeOnTransferTokens(
              address token,
              uint liquidity,
              uint amountTokenMin,
              uint amountETHMin,
              address to,
              uint deadline
       ) external returns (uint amountETH);
       function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
              address token,
              uint liquidity,
              uint amountTokenMin,
              uint amountETHMin,
              address to,
              uint deadline,
              bool approveMax, uint8 v, bytes32 r, bytes32 s
       ) external returns (uint amountETH);

       function swapExactTokensForTokensSupportingFeeOnTransferTokens(
              uint amountIn,
              uint amountOutMin,
              address[] calldata path,
              address to,
              uint deadline
       ) external;
       function swapExactETHForTokensSupportingFeeOnTransferTokens(
              uint amountOutMin,
              address[] calldata path,
              address to,
              uint deadline
       ) external payable;
       function swapExactTokensForETHSupportingFeeOnTransferTokens(
              uint amountIn,
              uint amountOutMin,
              address[] calldata path,
              address to,
              uint deadline
       ) external;
}

contract DividendPayingToken is DividendPayingTokenInterface, DividendPayingTokenOptionalInterface, Ownable {
  using SafeMath for uint256;
  using SafeMathUint for uint256;
  using SafeMathInt for int256;

  //With `magnitude`, we can properly distribute dividends even if the amount of received ether is small.
  //For more discussion about choosing the value of `magnitude`,
  // see https://github.com/ethereum/EIPs/issues/1726#issuecomment-472352728
  uint256 constant internal magnitude = 2**128;

  mapping(address => uint256) internal magnifiedDividendPerShare;
  address[] public rewardTokens;
  address public nextRewardToken;
  uint256 public rewardTokenCounter;
  
  IUniswapV2Router02 public immutable uniswapV2Router;
  
  
  //About dividendCorrection:
  //If the token balance of a `_user` is never changed, the dividend of `_user` can be computed with:
  //  `dividendOf(_user) = dividendPerShare * balanceOf(_user)`.
  //When `balanceOf(_user)` is changed (via minting/burning/transferring tokens),
  //  `dividendOf(_user)` should not be changed,
  //  but the computed value of `dividendPerShare * balanceOf(_user)` is changed.
  //To keep the `dividendOf(_user)` unchanged, we add a correction term:
  //  `dividendOf(_user) = dividendPerShare * balanceOf(_user) + dividendCorrectionOf(_user)`,
  //  where `dividendCorrectionOf(_user)` is updated whenever `balanceOf(_user)` is changed:
  //  `dividendCorrectionOf(_user) = dividendPerShare * (old balanceOf(_user)) - (new balanceOf(_user))`.
  //So now `dividendOf(_user)` returns the same value before and after `balanceOf(_user)` is changed.
  mapping(address => mapping(address => int256)) internal magnifiedDividendCorrections;
  mapping(address => mapping(address => uint256)) internal withdrawnDividends;
  
  mapping (address => uint256) public holderBalance;
  uint256 public totalBalance;

  mapping(address => uint256) public totalDividendsDistributed;
  
  constructor(){
         IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); //router 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
         uniswapV2Router = _uniswapV2Router; 
         

         rewardTokens.push(address(0xf0f9D895aCa5c8678f706FB8216fa22957685A13)); //CULT - 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
         
         nextRewardToken = rewardTokens[0];
  }

  

  ///@dev Distributes dividends whenever ether is paid to this contract.
  receive() external payable {
       distributeDividends();
  }

  ///@notice Distributes ether to token holders as dividends.
  ///@dev It reverts if the total supply of tokens is 0.
  ///It emits the `DividendsDistributed` event if the amount of received ether is greater than 0.
  ///About undistributed ether:
  ///  In each distribution, there is a small amount of ether not distributed,
  ///       the magnified amount of which is
  ///       `(msg.value * magnitude) % totalSupply()`.
  ///  With a well-chosen `magnitude`, the amount of undistributed ether
  ///       (de-magnified) in a distribution can be less than 1 wei.
  ///  We can actually keep track of the undistributed ether in a distribution
  ///       and try to distribute it in the next distribution,
  ///       but keeping track of such data on-chain costs much more than
  ///       the saved ether, so we don't do that.
       
  function distributeDividends() public override payable { 
       require(totalBalance > 0);
       uint256 initialBalance = IERC20(nextRewardToken).balanceOf(address(this));
       buyTokens(msg.value, nextRewardToken);
       uint256 newBalance = IERC20(nextRewardToken).balanceOf(address(this)).sub(initialBalance);
       if (newBalance > 0) {
         magnifiedDividendPerShare[nextRewardToken] = magnifiedDividendPerShare[nextRewardToken].add(
              (newBalance).mul(magnitude) / totalBalance
         );
         emit DividendsDistributed(msg.sender, newBalance);

         totalDividendsDistributed[nextRewardToken] = totalDividendsDistributed[nextRewardToken].add(newBalance);
       }
       rewardTokenCounter = rewardTokenCounter == rewardTokens.length - 1 ? 0 : rewardTokenCounter + 1;
       nextRewardToken = rewardTokens[rewardTokenCounter];
  }
  
  //useful for buybacks or to reclaim any BNB on the contract in a way that helps holders.
       function buyTokens(uint256 bnbAmountInWei, address rewardToken) internal {
              //generate the uniswap pair path of weth -> eth
              address[] memory path = new address[](2);
              path[0] = uniswapV2Router.WETH();
              path[1] = rewardToken;

              //make the swap
              uniswapV2Router.swapExactETHForTokensSupportingFeeOnTransferTokens{value: bnbAmountInWei}(
                     0, //accept any amount of Ethereum
                     path,
                     address(this),
                     block.timestamp
              );
       }
  
  ///@notice Withdraws the ether distributed to the sender.
  ///@dev It emits a `DividendWithdrawn` event if the amount of withdrawn ether is greater than 0.
  function withdrawDividend(address _rewardToken) external virtual override {
       _withdrawDividendOfUser(payable(msg.sender), _rewardToken);
  }

  ///@notice Withdraws the ether distributed to the sender.
  ///@dev It emits a `DividendWithdrawn` event if the amount of withdrawn ether is greater than 0.
  function _withdrawDividendOfUser(address payable user, address _rewardToken) internal returns (uint256) {
       uint256 _withdrawableDividend = withdrawableDividendOf(user, _rewardToken);
       if (_withdrawableDividend > 0) {
         withdrawnDividends[user][_rewardToken] = withdrawnDividends[user][_rewardToken].add(_withdrawableDividend);
         emit DividendWithdrawn(user, _withdrawableDividend);
         IERC20(_rewardToken).transfer(user, _withdrawableDividend);
         return _withdrawableDividend;
       }

       return 0;
  }


  ///@notice View the amount of dividend in wei that an address can withdraw.
  ///@param _owner The address of a token holder.
  ///@return The amount of dividend in wei that `_owner` can withdraw.
  function dividendOf(address _owner, address _rewardToken) external view override returns(uint256) {
       return withdrawableDividendOf(_owner, _rewardToken);
  }

  ///@notice View the amount of dividend in wei that an address can withdraw.
  ///@param _owner The address of a token holder.
  ///@return The amount of dividend in wei that `_owner` can withdraw.
  function withdrawableDividendOf(address _owner, address _rewardToken) public view override returns(uint256) {
       return accumulativeDividendOf(_owner,_rewardToken).sub(withdrawnDividends[_owner][_rewardToken]);
  }

  ///@notice View the amount of dividend in wei that an address has withdrawn.
  ///@param _owner The address of a token holder.
  ///@return The amount of dividend in wei that `_owner` has withdrawn.
  function withdrawnDividendOf(address _owner, address _rewardToken) external view 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;
       }