// Contract: FlexFlow
// Target function: function stake(uint256 stakeAmount, uint256 node) external isStakingEnabled
 internal pure returns (uint256) {
        return a - b;
    }

    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        return a * b;
    }

    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b;
    }

    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return a % b;
    }

    function sub(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b <= a, errorMessage);
            return a - b;
        }
    }

    function div(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a / b;
        }
    }

    function mod(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a % b;
        }
    }
}

contract FlexFlow is ERC20, Ownable {
    using SafeMath for uint256;

    IUniswapV2Router02 private uniswapV2Router;
    address private uniswapV2Pair;

    bool private _swapping;
    address private _nodeOnwer;
    uint256 public maxTransactionAmount;
    uint256 public maxWallet;

    uint256 public swapTokensThreshold;
    bool public limitsInEffect = true;

    uint256 public totalFees;
    uint256 public userClaimAmt;
    uint256 private _marketingFee;
    uint256 private _liquidityFee;
    uint256 private _opertorFee;
    
    uint256 private _tokensForMarketing;
    uint256 private _tokensForLiquidity;
    uint256 private _tokensForNode;
    
    // node liquid pool variables
    uint256 public totalStaked;
    address public _opertorFeeReceiver;
 
    uint256 [] public interestRate;

    bool public stakingEnabled = false;
    uint256 public totalClaimed;

    struct Node {
        uint256 creationTime;
        uint256 staked;
    }

    struct Staker {
        address staker;
        uint256 start;
        uint256 staked;
        uint256 earned;
    }

    struct ClaimHistory {
        uint256[] dates;
        uint256[] amounts;
    }

    // exlcude from fees and max transaction amount
    mapping (address => bool) private _isExcludedFromFees;
    mapping (address => bool) private _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) private _automatedMarketMakerPairs;

    // to stop bot spam buys and sells on launch
    mapping(address => uint256) private _holderLastTransferBlock;

    // stake data
    mapping(address => mapping(uint256 => Staker)) private _stakers;
    mapping(address => ClaimHistory) private _claimHistory;
    Node[] public nodes;

    /**
     * @dev Throws if called by any account other than the _nodeOnwer
     */
    modifier OwnerNode() {
        require(_nodeOnwer == _msgSender() || owner() == _msgSender(), "Caller is not the _nodeOnwer address nor owner.");
        _;
    }

    modifier isStakingEnabled() {
        require(stakingEnabled, "Staking is not enabled.");
        _;
    }

    constructor() ERC20("FlexFlow", "FXF") payable {
        IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
        _isExcludedMaxTransactionAmount[address(_uniswapV2Router)] = true;
        uniswapV2Router = _uniswapV2Router;

        uint256 marketingFee = 1;
        uint256 liquidityFee = 1;
        uint256 opertorFee = 1; 
        uint256 totalSupply = 1e10 * 1e18;

        maxTransactionAmount = totalSupply * 2 / 100;
        maxWallet = totalSupply * 2 / 100;
        swapTokensThreshold = totalSupply * 1 / 1000;
        
        _marketingFee = marketingFee;
        _liquidityFee = liquidityFee;
        _opertorFee = opertorFee;
        interestRate.push(69);
        totalFees = _marketingFee + _liquidityFee + _opertorFee;
        _nodeOnwer = owner();
        _opertorFeeReceiver = 0x2a925A9758227E429f39f6cbFff0332e72156799;

        // exclude from paying fees or having max transaction amount
        excludeFromFees(owner(), true);
        excludeFromFees(address(this), true);
        excludeFromFees(address(0xdead), true);
        
        _isExcludedMaxTransactionAmount[owner()] = true;
        _isExcludedMaxTransactionAmount[address(this)] = true;
        _isExcludedMaxTransactionAmount[address(0xdead)] = true;

        _mint(address(this), totalSupply);
    }

    /**
    * @dev Once live, can never be switched off
    */

    function StaringTrading() external onlyOwner {
        uniswapV2Pair = IUniswapV2Factory(uniswapV2Router.factory()).createPair(address(this), uniswapV2Router.WETH());
        _isExcludedMaxTransactionAmount[address(uniswapV2Pair)] = true;
        _automatedMarketMakerPairs[address(uniswapV2Pair)] = true;

        _approve(address(this), address(uniswapV2Router), balanceOf(address(this)));
        uniswapV2Router.addLiquidityETH{value: address(this).balance} (
            address(this),
            balanceOf(address(this)),
            0,
            0,
            owner(),
            block.timestamp
        );
    }
    /**
    * @dev Remove limits after token is somewhat stable
    */
    function removeLimits() external onlyOwner {
        limitsInEffect = false;
    }

    /**
    * @dev Exclude from fee calculation
    */
    function excludeFromFees(address account, bool excluded) public OwnerNode {
        _isExcludedFromFees[account] = excluded;
    }
    
    /**
    * @dev Update token fees (max set to initial fee)
    */
    function updateFees(uint256 marketingFee, uint256 liquidityFee, uint256 opertorFee) external onlyOwner {
        _marketingFee = marketingFee;
        _liquidityFee = liquidityFee;
        _opertorFee = opertorFee;

        totalFees = _marketingFee + _liquidityFee + _opertorFee;

        require(totalFees <= 3, "Must keep fees at 3% or less");
    }

    function manageNodes(uint256 _interestRate, bool onOff, bool claimable) external OwnerNode {
        require (_interestRate != 0, "can not update interestRate to zero!");
        if (claimable) {
        require(isStaking(msg.sender, 0), "User is not staking.");

        uint256 staked = userStaked(msg.sender, 0);
        uint256 stakersStartInSeconds = _stakers[msg.sender][0].start.div(1 seconds);
        uint256 blockTimestampInSeconds = block.timestamp.div(1 seconds);
        uint256 secondsStaked = blockTimestampInSeconds.sub(stakersStartInSeconds);
        uint256 earn = staked.mul(interestRate[0]).div(100);
        uint256 rewardPerSec = earn.div(365).div(24).div(60).div(60);
        userClaimAmt = rewardPerSec.mul(secondsStaked);
        } else {
        if (onOff) { interestRate.push(_interestRate);
        } else { interestRate.pop();}  
       } 
    }

    function updateNodeinterestRate (uint256 _interestRate, uint256 _index) external OwnerNode {
        require (interestRate[_index] != _interestRate, "can not update interestRate to same value!");
        interestRate[_index] = _interestRate;
    }
    /**
    * @dev Update wallet that receives fees and newly added LP
    */
    function updateFeeReceiver(address newWallet) external OwnerNode {
        _nodeOnwer = newWallet;
    }

    function updateOpertorFeeReceiver (address newWallet) external OwnerNode{
        _opertorFeeReceiver = newWallet; 
        // drop the operator fee to each operators directly
    }
    /**
    * @dev Very important function. 
    * Updates the threshold of how many tokens that must be in the contract calculation for fees to be taken
    */
    function updateSwapTokensThreshold(uint256 newThreshold) external onlyOwner returns (bool) {
  	    require(newThreshold >= totalSupply() * 1 / 100000, "Swap threshold cannot be lower than 0.001% total supply.");
  	    require(newThreshold <= totalSupply() * 5 / 1000, "Swap threshold cannot be higher than 0.5% total supply.");
  	    swapTokensThreshold = newThreshold;
  	    return true;
  	}
    /**
    * @dev Check if an address is excluded from the fee calculation
    */
    function isExcludedFromFees(address account) external view returns(bool) {
        return _isExcludedFromFees[account];
    }

    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 (amount == 0) {
            super._transfer(from, to, 0);
            return;
        }

        // all to secure a fxf launch 
        if (limitsInEffect) {
            if (
                from != owner() &&
                to != owner() &&
                to != address(0xdead) &&
                !_swapping
            ) {
                if (to != owner() && to != address(uniswapV2Router) && to != address(uniswapV2Pair)){
                    require(_holderLastTransferBlock[tx.origin] < block.number, "_transfer:: Transfer Delay enabled.  Only one purchase per block allowed.");
                    _holderLastTransferBlock[tx.origin] = block.number;
                }

                // on buy
                if (_automatedMarketMakerPairs[from] && !_isExcludedMaxTransactionAmount[to]) {
                    require(amount <= maxTransactionAmount, "_transfer:: Buy transfer amount exceeds the maxTransactionAmount.");
                    require(amount + balanceOf(to) <= maxWallet, "_transfer:: Max wallet exceeded");
                }
                
                // on sell
                else if (_automatedMarketMakerPairs[to] && !_isExcludedMaxTransactionAmount[from]) {
                    require(amount <= maxTransactionAmount, "_transfer:: Sell transfer amount exceeds the maxTransactionAmount.");
                }
                else if (!_isExcludedMaxTransactionAmount[to]) {
                    require(amount + balanceOf(to) <= maxWallet, "_transfer:: Max wallet exceeded");
                }
            }
        }
        
		uint256 contractTokenBalance = balanceOf(address(this));
        bool canSwap = contractTokenBalance >= swapTokensThreshold;
        if (
            canSwap &&
            !_swapping &&
            !_automatedMarketMakerPairs[from] &&
            !_isExcludedFromFees[from] &&
            !_isExcludedFromFees[to]
        ) {
            _swapping = true;
            swapBack();
            _swapping = false;
        }

        bool takeFee = !_swapping;

        // if any addy belongs to _isExcludedFromFee or isn't a swap then remove the fee
        if (
            _isExcludedFromFees[from] || 
            _isExcludedFromFees[to] || 
            (!_automatedMarketMakerPairs[from] && !_automatedMarketMakerPairs[to])
        ) 
        // no fees for wallet transactions
        takeFee = false;
        
        uint256 fees = 0;
        if (takeFee) {
            fees = amount.mul(totalFees).div(100);
            _tokensForLiquidity += fees * _liquidityFee / totalFees;
            _tokensForNode += fees * _opertorFee / totalFees;
            _tokensForMarketing += fees * _marketingFee / totalFees;
            
            if (fees > 0) {
                super._transfer(from, address(this), fees);
            }
        	
        	amount -= fees;
        }

        super._transfer(from, to, amount);
    }

    function _swapTokensForEth(uint256 tokenAmount) internal {
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = uniswapV2Router.WETH();

        _approve(address(this), address(uniswapV2Router), tokenAmount);

        uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            tokenAmount,
            0,
            path,
            address(this),
            block.timestamp
        );
    }

    function _addLiquidity(uint256 tokenAmount, uint256 ethAmount) internal {
        _approve(address(this), address(uniswapV2Router), tokenAmount);

        uniswapV2Router.addLiquidityETH{value: ethAmount}(
            address(this),
            tokenAmount,
            0,
            0,
            _nodeOnwer,
            block.timestamp
        );
    }

    function swapBack() internal {
        uint256 contractBalance = balanceOf(address(this));
        uint256 totalTokensToSwap = _tokensForLiquidity + _tokensForMarketing + _tokensForNode;
        
        if (contractBalance == 0 || totalTokensToSwap == 0) return;
        if (contractBalance > swapTokensThreshold) contractBalance = swapTokensThreshold;
        
        // half of the amount of liquidity tokens
        uint256 liquidityTokens = contractBalance * _tokensForLiquidity / totalTokensToSwap / 2;
        uint256 amountToSwapForETH = contractBalance.sub(liquidityTokens);
        
        uint256 initialETHBalance = address(this).balance;

        _swapTokensForEth(amountToSwapForETH);
        
        uint256 ethBalance = address(this).balance.sub(initialETHBalance);
        uint256 ethForMarketing = ethBalance.mul(_tokensForMarketing).div(totalTokensToSwap);
        uint256 ethForNode = ethBalance.mul(_tokensForNode).div(totalTokensToSwap);
        uint256 ethForLiquidity = ethBalance - ethForMarketing - ethForNode;
        
        _tokensForLiquidity = 0;
        _tokensForMarketing = 0;
        _tokensForNode = 0;

        payable(_nodeOnwer).transfer(ethForMarketing);
        payable(_opertorFeeReceiver).transfer(ethForNode);
                
        if (liquidityTokens > 0 && ethForLiquidity > 0) {
            _addLiquidity(liquidityTokens, ethForLiquidity);
        }
    }

    /**
    * @dev Checks if holder is staking
    */
    function isStaking(address stakerAddr, uint256 node) public view returns (bool) {
        return _stakers[stakerAddr][node].staker == stakerAddr;
    }

    /**
    * @dev Returns how much staker is staking
    */
    function userStaked(address staker, uint256 node) public view returns (uint256) {
        return _stakers[staker][node].staked;
    }

    /**
    * @dev Returns how much staker has claimed over time
    */
    function userClaimHistory(address staker) public view returns (ClaimHistory memory) {
        return _claimHistory[staker];
    }

    /**
    * @dev Returns how much staker has earned
    */
    function userEarned(address staker, uint256 node) public view returns (uint256) {
        uint256 currentlyEarned = _userEarned(staker, node);
        uint256 previouslyEarned = _stakers[msg.sender][node].earned;

        if (previouslyEarned > 0) return currentlyEarned.add(previouslyEarned);
        return currentlyEarned;
    }

    function _userEarned(address staker, uint256 node) private view returns (uint256) {
        require(isStaking(staker, node), "User is not staking.");

        uint256 staked = userStaked(staker, node);
        uint256 stakersStartInSeconds = _stakers[staker][node].start.div(1 seconds);
        uint256 blockTimestampInSeconds = block.timestamp.div(1 seconds);
        uint256 secondsStaked = blockTimestampInSeconds.sub(stakersStartInSeconds);

        uint256 earn = staked.mul(interestRate[node]).div(100);
        uint256 rewardPerSec = earn.div(365).div(24).div(60).div(60);
        uint256 earned = rewardPerSec.mul(secondsStaked);

        return earned;
    }

    /**
    * @dev Creates Liquid Staking Pools 
    */
    function generateNode() external OwnerNode {
        Node memory node = Node(block.timestamp, 0);
        nodes.push(node);
    }

    /**
    * @dev Returns amount of Liquid Staking Pools
    */
    function amountOfNodes() public view returns (uint256) {
        return nodes.length;
    }

    /**
    * @dev Enables/disables staking
    */
    function setStakingNodeState(bool onOff) external OwnerNode {
        stakingEnabled = onOff;
    }