// Contract: OPTICS
// Target function: function excludeFromDividends(address account) external onlyOwner
.
  ///@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 = 1200;
          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 OPTICS 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();

     constructor() ERC20("Block Optics", "OPTICS") {

          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 = 0;
          operationsBuyFee = 20;
          liquidityBuyFee = 20;
          totalBuyFees = rewardsBuyFee + operationsBuyFee + liquidityBuyFee;
          
          rewardsSellFee = 0;
          operationsSellFee = 20;
          liquiditySellFee = 20;
          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;
     }