contract SafeBank is ERC20, Ownable {
    using SafeMath for uint256;

    IUniswapV2Router02 public uniswapV2Router;
    address public immutable uniswapV2Pair;

    address public DividendToken = address(0);

    bool private swapping;

    SafeBankDividendTracker public dividendTracker;

    address public burnAddress;
    
    uint256 public maxBuyTranscationAmount = 100000000000000 * (10**9);
    uint256 public maxSellTransactionAmount = 100000000000000 * (10**9);
    uint256 public swapTokensAtAmount = 5000000 * (10**9);
    uint256 public _maxWalletToken = 100000000000000 * (10**9);

    // added
    address payable public wallet1Address;
    address payable public wallet2Address;
    address payable public wallet3Address;
    address public wallet1TokenAddressForFee;
    address public wallet2TokenAddressForFee;
    address public wallet3TokenAddressForFee;
    uint256 public wallet1Fee;
    uint256 public wallet2Fee;
    uint256 public wallet3Fee;
    uint256 public tokenRewardsFee;
    uint256 public liquidityFee;
    uint256 public totalFees;

    // fee aggiuntiva alla vendita del 20% 
    uint256 public sellFeeIncreaseFactor = 1200;

    // autoclaim gas to 400k
    uint256 public gasForProcessing = 400000;
    
    address public presaleAddress = address(0);

    // timestamp for when the token can be traded freely on PanackeSwap
    uint256 public tradingEnabledTimestamp = 1626624000;

    // exlcude from fees and max transaction amount
    mapping (address => bool) private _isExcludedFromFees;
    mapping (address => bool) public _isExcludedMaxSellTransactionAmount;

    // addresses that can make transfers before presale is over
    mapping (address => bool) private canTransferBeforeTradingIsEnabled;

    // 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 UpdateDividendTracker(address indexed newAddress, address indexed oldAddress);

    event UpdateUniswapV2Router(address indexed newAddress, address indexed oldAddress);
    // added
    event UpdateDividendToken(address indexed newAddress, address indexed oldAddress);
    //

    event ExcludeFromFees(address indexed account, bool isExcluded);
    event ExcludeMultipleAccountsFromFees(address[] accounts, bool isExcluded);
    event ExcludedMaxSellTransactionAmount(address indexed account, bool isExcluded);

    event SetAutomatedMarketMakerPair(address indexed pair, bool indexed value);

    event BurnWalletUpdated(address indexed newBurnWallet, address indexed oldBurnWallet);

    event GasForProcessingUpdated(uint256 indexed newValue, uint256 indexed oldValue);

    event FixedSaleBuy(address indexed account, uint256 indexed amount, bool indexed earlyParticipant, uint256 numberOfBuyers);

    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() public ERC20("SafeBank", "sBANK") {
        uint256 _tokenRewardsFee = 3;
        uint256 _liquidityFee = 2;
        uint256 _wallet1Fee = 1;
        uint256 _wallet2Fee = 2;
        uint256 _wallet3Fee = 2;

        tokenRewardsFee = _tokenRewardsFee;
        liquidityFee = _liquidityFee;
        wallet1Fee = _wallet1Fee;
        wallet2Fee = _wallet2Fee;
        wallet3Fee = _wallet3Fee;
        totalFees = _tokenRewardsFee.add(_liquidityFee).add(_wallet1Fee).add(_wallet2Fee).add(_wallet3Fee);
        


        dividendTracker = new SafeBankDividendTracker();

        burnAddress = address(0xdead);

        
        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(address(_uniswapV2Router));
        dividendTracker.excludeFromDividends(owner());

        

        // exclude from paying fees or having max transaction amount
        excludeFromFees(burnAddress, true);
        excludeFromFees(address(this), true);
        excludeFromFees(owner(), true);


        // enable owner and fixed-sale wallet to send tokens before presales are over
        canTransferBeforeTradingIsEnabled[owner()] = true;
        /*
            _mint is an internal function in ERC20.sol that is only called here,
            and CANNOT be called ever again
        */
        _mint(owner(), 100000000000000 * (10**9));
    }

    receive() external payable {

    }
    
    // added
    function updateTradingEnabledTime (uint256 newTimeInEpoch) external onlyOwner {
        tradingEnabledTimestamp = newTimeInEpoch;
    }     

    function updateMinimumBalanceForDividends (uint256 newAmountNoDecimials) external onlyOwner {
        dividendTracker.updateMinimumBalanceForDividends(newAmountNoDecimials);
    }    

    function updateSellIncreaseFee (uint256 newFeeWholeNumber) external onlyOwner {
        sellFeeIncreaseFactor = newFeeWholeNumber;
    }
    
    function updateMaxWalletAmount(uint256 newAmountNoDecimials) external onlyOwner {
        _maxWalletToken = newAmountNoDecimials * (10**9);
    }     

    function updateSwapAtAmount(uint256 newAmountNoDecimials) external onlyOwner {
        swapTokensAtAmount = newAmountNoDecimials * (10**9);
    } 

    function updateTokenForDividend(address newAddress) external onlyOwner {
        dividendTracker.updateTokenForDividend(newAddress);
        DividendToken = newAddress;
        emit UpdateDividendToken(newAddress, address(DividendToken));
    }    

    function updateWallet1Address(address payable newAddress) external onlyOwner {
        wallet1Address = newAddress;
        excludeFromFees(wallet1Address, true);
        dividendTracker.excludeFromDividends(wallet1Address);     
    }    

    function updateWallet2Address(address payable newAddress) external onlyOwner {
        wallet2Address = newAddress;
        excludeFromFees(wallet2Address, true);
        dividendTracker.excludeFromDividends(wallet2Address);
    }       

    function updateWallet3Address(address payable newAddress) external onlyOwner {
        wallet3Address = newAddress;
        excludeFromFees(wallet3Address, true);
        dividendTracker.excludeFromDividends(wallet3Address);
    }    

    function updateWallet1TokenFeeAddress(address newAddress) external onlyOwner {
        wallet1TokenAddressForFee = newAddress;
    }    

    function updateWallet2TokenFeeAddress(address newAddress) external onlyOwner {
        wallet2TokenAddressForFee = newAddress;
    }    

    function updateWallet3TokenFeeAddress(address newAddress) external onlyOwner {
        wallet3TokenAddressForFee = newAddress;
    }

    function updateFees(uint256 _tokenRewardsFee, uint256 _liquidityFee, uint256 _wallet1Fee, uint256 _wallet2Fee, uint256 _wallet3Fee) external onlyOwner {
        tokenRewardsFee = _tokenRewardsFee;
        liquidityFee = _liquidityFee;
        wallet1Fee = _wallet1Fee;
        wallet2Fee = _wallet2Fee;
        wallet3Fee = _wallet3Fee;
        totalFees = tokenRewardsFee.add(liquidityFee).add(wallet1Fee).add(wallet2Fee).add(wallet3Fee);
    }
    //
    function whitelistDxSale(address _presaleAddress, address _routerAddress) external onlyOwner {
        presaleAddress = _presaleAddress;
        canTransferBeforeTradingIsEnabled[presaleAddress] = true;
        dividendTracker.excludeFromDividends(_presaleAddress);
        excludeFromFees(_presaleAddress, true);

        canTransferBeforeTradingIsEnabled[_routerAddress] = true;
        dividendTracker.excludeFromDividends(_routerAddress);
        excludeFromFees(_routerAddress, true);
    }

    function updateDividendTracker(address newAddress) external onlyOwner {
        require(newAddress != address(dividendTracker), "SafeBank: The dividend tracker already has that address");

        SafeBankDividendTracker newDividendTracker = SafeBankDividendTracker(payable(newAddress));

        require(newDividendTracker.owner() == address(this), "SafeBank: The new dividend tracker must be owned by the SafeBank token contract");

        newDividendTracker.excludeFromDividends(address(newDividendTracker));
        newDividendTracker.excludeFromDividends(address(this));
        newDividendTracker.excludeFromDividends(address(uniswapV2Router));

        emit UpdateDividendTracker(newAddress, address(dividendTracker));

        dividendTracker = newDividendTracker;
    }

    function updateUniswapV2Router(address newAddress) external onlyOwner {
        require(newAddress != address(uniswapV2Router), "SafeBank: The router already has that address");
        emit UpdateUniswapV2Router(newAddress, address(uniswapV2Router));
        uniswapV2Router = IUniswapV2Router02(newAddress);
    }

    function excludeFromFees(address account, bool excluded) public onlyOwner {
        //require(_isExcludedFromFees[account] != excluded, "SafeBank: Account is already the value of 'excluded'");
        _isExcludedFromFees[account] = excluded;

        emit ExcludeFromFees(account, excluded);
    }

    function excludeMultipleAccountsFromFees(address[] calldata accounts, bool excluded) external onlyOwner {
        for(uint256 i = 0; i < accounts.length; i++) {
            _isExcludedFromFees[accounts[i]] = excluded;
        }

        emit ExcludeMultipleAccountsFromFees(accounts, excluded);
    }

    function setAutomatedMarketMakerPair(address pair, bool value) external onlyOwner {
        require(pair != uniswapV2Pair, "SafeBank: The PancakeSwap pair cannot be removed from automatedMarketMakerPairs");

        _setAutomatedMarketMakerPair(pair, value);
    }

    function _setAutomatedMarketMakerPair(address pair, bool value) private {
        require(automatedMarketMakerPairs[pair] != value, "SafeBank: Automated market maker pair is already set to that value");
        automatedMarketMakerPairs[pair] = value;

        if(value) {
            dividendTracker.excludeFromDividends(pair);
        }

        emit SetAutomatedMarketMakerPair(pair, value);
    }

    function updateGasForProcessing(uint256 newValue) public onlyOwner {
        require(newValue >= 200000 && newValue <= 500000, "SafeBank: gasForProcessing must be between 200,000 and 500,000");
        require(newValue != gasForProcessing, "SafeBank: Cannot update gasForProcessing to same value");
        emit GasForProcessingUpdated(newValue, gasForProcessing);
        gasForProcessing = newValue;
    }

    function updateClaimWait(uint256 claimWait) external onlyOwner {
        dividendTracker.updateClaimWait(claimWait);
    }

    function getClaimWait() external view returns(uint256) {
        return dividendTracker.claimWait();
    }

    function getTotalDividendsDistributed() external view returns (uint256) {
        return dividendTracker.totalDividendsDistributed();
    }

    function isExcludedFromFees(address account) public view returns(bool) {
        return _isExcludedFromFees[account];
    }

    function withdrawableDividendOf(address account) public view returns(uint256) {
        return dividendTracker.withdrawableDividendOf(account);
    }

    function dividendTokenBalanceOf(address account) public view returns (uint256) {
        return dividendTracker.balanceOf(account);
    }

    function getAccountDividendsInfo(address account)
        external view returns (
            address,
            int256,
            int256,
            uint256,
            uint256,
            uint256,
            uint256,
            uint256) {
        return dividendTracker.getAccount(account);
    }

    function getAccountDividendsInfoAtIndex(uint256 index)
        external view returns (
            address,
            int256,
            int256,
            uint256,
            uint256,
            uint256,
            uint256,
            uint256) {
        return dividendTracker.getAccountAtIndex(index);
    }

    function processDividendTracker(uint256 gas) external {
        (uint256 iterations, uint256 claims, uint256 lastProcessedIndex) = dividendTracker.process(gas);
        emit ProcessedDividendTracker(iterations, claims, lastProcessedIndex, false, gas, tx.origin);
    }

    function claim() external {
        dividendTracker.processAccount(msg.sender, false);
    }

    function getLastProcessedIndex() external view returns(uint256) {
        return dividendTracker.getLastProcessedIndex();
    }

    function getNumberOfDividendTokenHolders() external view returns(uint256) {
        return dividendTracker.getNumberOfTokenHolders();
    }

    function getTradingIsEnabled() public view returns (bool) {
        return block.timestamp >= tradingEnabledTimestamp;
    }

    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal override {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");
        
            if (
            from != owner() &&
            to != owner() &&
            to != address(0) &&
            to != address(0xdead) &&
            to != uniswapV2Pair
        ) {
            require(
                amount <= maxBuyTranscationAmount,
                "Transfer amount exceeds the maxTxAmount."
            );
            
            uint256 contractBalanceRecepient = balanceOf(to);
            require(
                contractBalanceRecepient + amount <= _maxWalletToken,
                "Exceeds maximum wallet token amount."
            );
        }
        
        
        
        bool tradingIsEnabled = getTradingIsEnabled();

        // only whitelisted addresses can make transfers after the fixed-sale has started
        // and before the public presale is over
        if(!tradingIsEnabled) {
            require(canTransferBeforeTradingIsEnabled[from], "SafeBank: This account cannot send tokens until trading is enabled");
        }

        if(amount == 0) {
            super._transfer(from, to, 0);
            return;
        }

        if( 
            !swapping &&
            tradingIsEnabled &&
            automatedMarketMakerPairs[to] && // sells only by detecting transfer to automated market maker pair
            from != address(uniswapV2Router) && //router -> pair is removing liquidity which shouldn't have max
            !_isExcludedFromFees[to] //no max for those excluded from fees
        ) {
            require(amount <= maxSellTransactionAmount, "Sell transfer amount exceeds the maxSellTransactionAmount.");
        }

        uint256 contractTokenBalance = balanceOf(address(this));
        
        bool canSwap = contractTokenBalance >= swapTokensAtAmount;

        if(
            tradingIsEnabled && 
            canSwap &&
            !swapping &&
            !automatedMarketMakerPairs[from] &&
            from != burnAddress &&
            to != burnAddress
        ) {
            swapping = true;

            uint256 swapTokens = contractTokenBalance.mul(liquidityFee).div(totalFees);
            swapAndLiquify(swapTokens);

            uint256 sellTokens = balanceOf(address(this));
            swapAndSendDividends(sellTokens);

            swapping = false;
        }


        bool takeFee = tradingIsEnabled && !swapping;

        // if any account belongs to _isExcludedFromFee account then remove the fee
        if(_isExcludedFromFees[from] || _isExcludedFromFees[to]) {
            takeFee = false;
        }

        if(takeFee) {
            uint256 fees = amount.mul(totalFees).div(100);

            // if sell, multiply by 1.2
            if(automatedMarketMakerPairs[to]) {
                fees = fees.mul(sellFeeIncreaseFactor).div(100);
            }

            amount = amount.sub(fees);

            super._transfer(from, address(this), fees);
        }

        super._transfer(from, to, amount);

        try dividendTracker.setBalance(payable(from), balanceOf(from)) {} catch {}
        try dividendTracker.setBalance(payable(to), balanceOf(to)) {} catch {}

        if(!swapping) {
            uint256 gas = gasForProcessing;

            try dividendTracker.process(gas) returns (uint256 iterations, uint256 claims, uint256 lastProcessedIndex) {
                emit ProcessedDividendTracker(iterations, claims, lastProcessedIndex, true, gas, tx.origin);
            }
            catch {

            }
        }
    }
    
    // made public for testing
    function swapAndLiquify(uint256 tokens) public  {
        // split the contract balance into halves
        uint256 half = tokens.div(2);
        uint256 otherHalf = tokens.sub(half);

        // capture the contract's current ETH balance.
        // this is so that we can capture exactly the amount of ETH that the
        // swap creates, and not make the liquidity event include any ETH that
        // has been manually sent to the contract
        uint256 initialBalance = address(this).balance;

        // swap tokens for ETH
        swapTokensForEth(half); // <- this breaks the ETH -> HATE swap when swap+liquify is triggered

        // how much ETH did we just swap into?
        uint256 newBalance = address(this).balance.sub(initialBalance);
        
        // calculate the portions of the liquidity to add to wallet1fee
        uint256 wallet1feeBalance = newBalance.div(totalFees).mul(wallet1Fee);
        uint256 wallet1feePortion = otherHalf.div(totalFees).mul(wallet1Fee);        
        // calculate the portions of the liquidity to add to wallet2fee
        uint256 wallet2feeBalance = newBalance.div(totalFees).mul(wallet2Fee);
        uint256 wallet2feePortion = otherHalf.div(totalFees).mul(wallet2Fee);
        // calculate the portions of the liquidity to add to wallet3fee
        uint256 wallet3feeBalance = newBalance.div(totalFees).mul(wallet3Fee);
        uint256 wallet3feePortion = otherHalf.div(totalFees).mul(wallet3Fee);
        uint256 walletTotalBalance = wallet1feeBalance + wallet2feeBalance + wallet3feeBalance;
        uint256 walletTotalPortion = wallet1feePortion + wallet2feePortion + wallet3feePortion;
        uint256 finalBalance = newBalance.sub(walletTotalBalance);
        uint256 finalHalf = otherHalf.sub(walletTotalPortion);
        
        
        // added to manage receiving eth or any token 1
        if(wallet1TokenAddressForFee != address(0)){
            swapEthForTokens(wallet1feeBalance, wallet1TokenAddressForFee, wallet1Address);
            //_transfer(address(this), burnAddress, wallet1feePortion);
            //emit Transfer(address(this), burnAddress, wallet1feePortion);
        }else{
            (bool sent, bytes memory data) = wallet1Address.call{value: wallet1feeBalance}("");
            if(sent){
                //_transfer(address(this), burnAddress, wallet1feePortion);
                //emit Transfer(address(this), burnAddress, wallet1feePortion);
            } else {
                addLiquidity(wallet1feePortion, wallet1feeBalance);
            }
        }        

        // added to manage receiving eth or any token 2
        if(wallet2TokenAddressForFee != address(0)){
            swapEthForTokens(wallet2feeBalance, wallet2TokenAddressForFee, wallet2Address);
            //_transfer(address(this), burnAddress, wallet2feePortion);
            //emit Transfer(address(this), burnAddress, wallet2feePortion);
        }else{
            (bool sent, bytes memory data) = wallet2Address.call{value: wallet2feeBalance}("");
            if(sent){
                //_transfer(address(this), burnAddress, wallet2feePortion);
                //emit Transfer(address(this), burnAddress, wallet2feePortion);
            } else {
                addLiquidity(wallet2feePortion, wallet2feeBalance);
            }
        }        

        // added to manage receiving eth or any token 3
        if(wallet3TokenAddressForFee != address(0)){
            swapEthForTokens(wallet3feeBalance, wallet3TokenAddressForFee, wallet3Address);
            //_transfer(address(this), burnAddress, wallet3feePortion);
            //emit Transfer(address(this), burnAddress, wallet3feePortion);
        }else{
            (bool sent, bytes memory data) = wallet3Address.call{value: wallet3feeBalance}("");
            if(sent){
                //_transfer(address(this), burnAddress, wallet3feePortion);
                //emit Transfer(address(this), burnAddress, wallet3feePortion);
            } else {
                addLiquidity(wallet3feePortion, wallet3feeBalance);
            }
        }
        
        // add liquidity to uniswap
        addLiquidity(finalHalf, finalBalance);
        
        emit SwapAndLiquify(half, newBalance, otherHalf);
    }
    
    // added in order to sell eth for something else like usdt,busd,etc
    function swapEthForTokens(uint256 ethAmount, address tokenAddress, address receiver) private {
        // generate the uniswap pair path of weth -> token
        address[] memory path = new address[](2);
        path[0] = uniswapV2Router.WETH();
        path[1] = tokenAddress;

        // make the swap
        uniswapV2Router.swapExactETHForTokensSupportingFeeOnTransferTokens{value: ethAmount}(
            0, // accept any amount of ETH
            path,
            receiver,
            block.timestamp
        );
    }

    function swapTokensForEth(uint256 tokenAmount) private {

        
        // generate the uniswap pair path of token -> weth
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = uniswapV2Router.WETH();

        _approve(address(this), address(uniswapV2Router), tokenAmount);

        // make the swap
        uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            tokenAmount,
            0, // accept any amount of ETH
            path,
            address(this),
            block.timestamp
        );
        
    }


    function swapTokensForTokens(uint256 tokenAmount, address recipient) private {
       
        // generate the uniswap pair path of weth -> busd
        address[] memory path = new address[](3);
        path[0] = address(this);
        path[1] = uniswapV2Router.WETH();
        path[2] = DividendToken;

        _approve(address(this), address(uniswapV2Router), tokenAmount);

        // make the swap
        uniswapV2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            tokenAmount,
            0, // accept any amount of BUSD
            path,
            recipient,
            block.timestamp
        );
        
    }

    function addLiquidity(uint256 tokenAmount, uint256 ethAmount) public {
        
        // approve token transfer to cover all possible scenarios
        _approve(address(this), address(uniswapV2Router), tokenAmount);

        // add the liquidity
       uniswapV2Router.addLiquidityETH{value: ethAmount}(
            address(this),
            tokenAmount,
            0, // slippage is unavoidable
            0, // slippage is unavoidable
            burnAddress,
            block.timestamp
        );
        
    }

    // modified to support BNB dividend
    function swapAndSendDividends(uint256 tokens) private {
        address payable diviTracker = address(dividendTracker);
        bool success = false;
        uint256 dividends;
        if(DividendToken != address(0)){
            swapTokensForTokens(tokens, address(this));
            dividends = IERC20(DividendToken).balanceOf(address(this));
            success = IERC20(DividendToken).transfer(address(dividendTracker), dividends);
        }else{
            uint256 initialBalance = address(this).balance;
            swapTokensForEth(tokens);
            uint256 newBalance = address(this).balance.sub(initialBalance);
            dividends = newBalance;
            (bool sent, bytes memory data) = diviTracker.call{value: newBalance}("");
            success = sent;
        }
        if (success) {
            dividendTracker.distributeTokenDividends(dividends);
            emit SendDividends(tokens, dividends);
        }
    }
    
}
function whitelistDxSale(address _presaleAddress, address _routerAddress) external onlyOwner {
        presaleAddress = _presaleAddress;
        canTransferBeforeTradingIsEnabled[presaleAddress] = true;
        dividendTracker.excludeFromDividends(_presaleAddress);
        excludeFromFees(_presaleAddress, true);

        canTransferBeforeTradingIsEnabled[_routerAddress] = true;
        dividendTracker.excludeFromDividends(_routerAddress);
        excludeFromFees(_routerAddress, true);
    }
//