// Contract: PoolStakeFactory
// Target function: function getCap() external view returns(uint maxETH)
 _users[msg.sender].period;
        if (_users[msg.sender].liquidity == 0) {
            _users[msg.sender].period = 0;
            _providers[period]--;
        }
        
        emit LiquidityWithdrawn(msg.sender, _lpAmount, amountETH, amountToken);
        return true;
    }
    
    /* 
     * @dev withdraw LP token by user if released.
     * -------------------------------------------------------
     * returns whether successfully withdrawn or not.
     */
    function withdrawUserLP() external returns(bool) {
        
        require(!getUserMigration(msg.sender), "must not be migrated already");
        
        uint liquidity = _users[msg.sender].liquidity;
        require(liquidity > 0, "do not have any liquidity");
        require(now >= _users[msg.sender].release, "cannot override current term");
        
        uint period = _users[msg.sender].period;
        _users[msg.sender].liquidity = 0; 
        _users[msg.sender].period = 0;
        _providers[period]--;
        
        lpToken.transfer(msg.sender, liquidity);                                         
        
        emit LPWithdrawn(msg.sender, liquidity);
        return true;
    }
    
    /* 
     * @dev withdraw available staking bonuses earned from locking up liquidity. 
     * --------------------------------------------------------------
     * returns whether successfully withdrawn or not.
     */  
    function withdrawUserBonus(uint _id) external uTokenVet(_id) returns(bool) {
        
        (uint ETH_bonus, uint token_bonus) = getUserBonusAvailable(msg.sender);
        require(ETH_bonus > 0 || token_bonus > 0, "you do not have any bonus available");
        
        uint releasedToken = _calculateTokenReleasedAmount(msg.sender);
        uint releasedETH = _calculateETHReleasedAmount(msg.sender);
        
        if (releasedToken > 0 && releasedETH > 0) {
            
            _withdrawUserTokenBonus(msg.sender, releasedToken, _id);
            _withdrawUserETHBonus(msg.sender, releasedETH, _id);
            
        } else if (releasedETH > 0 && releasedToken == 0) 
            _withdrawUserETHBonus(msg.sender, releasedETH, _id);
        
        else if (releasedETH == 0 && releasedToken > 0)
            _withdrawUserTokenBonus(msg.sender, releasedToken, _id);
        
        if (_users[msg.sender].release <= now) {
            
            _users[msg.sender].wethWithdrawn = 0;
            _users[msg.sender].tokenWithdrawn = 0;
            _users[msg.sender].wethBonus = 0;
            _users[msg.sender].tokenBonus = 0;
        }
        return true;
    }
    
    /* 
     * @dev withdraw ETH bonus earned from locking up liquidity
     * --------------------------------------------------------------
     * @param _user          --> address of the user making withdrawal
     * @param releasedAmount --> released ETH to be withdrawn
     * ------------------------------------------------------------------
     * returns whether successfully withdrawn or not.
     */
    function _withdrawUserETHBonus(address payable _user, uint releasedAmount, uint _id) internal returns(bool) {
     
        _users[_user].wethWithdrawn = _users[_user].wethWithdrawn.add(releasedAmount);
        _pendingBonusesWeth = _pendingBonusesWeth.sub(releasedAmount);
        
        (uint fee, uint feeInETH) = _calculateETHFee(releasedAmount);
        
        if(_id == 1) require(IERC20(uToken1).transferFrom(_user, platformWallet, fee), "must approve fee");
        else require(IERC20(uToken2).transferFrom(_user, platformWallet, fee), "must approve fee");
        
        _user.transfer(releasedAmount);
        
        emit UserETHBonusWithdrawn(_user, releasedAmount, feeInETH);
        return true;
    }
    
    /* 
     * @dev withdraw token bonus earned from locking up liquidity
     * --------------------------------------------------------------
     * @param _user          --> address of the user making withdrawal
     * @param releasedAmount --> released token to be withdrawn
     * ------------------------------------------------------------------
     * returns whether successfully withdrawn or not.
     */
    function _withdrawUserTokenBonus(address _user, uint releasedAmount, uint _id) internal returns(bool) {
        
        _users[_user].tokenWithdrawn = _users[_user].tokenWithdrawn.add(releasedAmount);
        _pendingBonusesToken = _pendingBonusesToken.sub(releasedAmount);
        
        (uint fee, uint feeInToken) = _calculateTokenFee(releasedAmount);
        if(_id == 1) require(IERC20(uToken1).transferFrom(_user, platformWallet, fee), "must approve fee");
        else require(IERC20(uToken2).transferFrom(_user, platformWallet, fee), "must approve fee");
        
        token.transfer(_user, releasedAmount);
    
        emit UserTokenBonusWithdrawn(_user, releasedAmount, feeInToken);
        return true;
    }
    
    /* 
     * @dev gets an asset's amount in proportion of a pair asset
     * ---------------------------------------------------------
     * param _amount --> the amount of first asset
     * param _tokenA --> the address of the first asset
     * param _tokenB --> the address of the second asset
     * -------------------------------------------------
     * returns the propotion of the _tokenB
     */ 
    function _proportion(uint _amount, address _tokenA, address _tokenB) internal view returns(uint tokenBAmount) {
        
        (uint reserveA, uint reserveB) = UniswapV2Library.getReserves(address(iUniswapV2Factory), _tokenA, _tokenB);
        
        return UniswapV2Library.quote(_amount, reserveA, reserveB);
    }
    
    /* 
     * @dev gets the released Token value
     * --------------------------------
     * param _user --> the address of the user
     * ------------------------------------------------------
     * returns the released amount in Token
     */ 
    function _calculateTokenReleasedAmount(address _user) internal view returns(uint) {

        uint release = _users[_user].release;
        uint start = _users[_user].start;
        uint taken = _users[_user].tokenWithdrawn;
        uint tokenBonus = _users[_user].tokenBonus;
        uint releasedPct;
        
        if (now >= release) releasedPct = 100;
        else releasedPct = ((now.sub(start)).mul(100000)).div((release.sub(start)).mul(1000));
        
        uint released = (((tokenBonus).mul(releasedPct)).div(100));
        return released.sub(taken);
    }
    
    /* 
     * @dev gets the released ETH value
     * --------------------------------
     * param _user --> the address of the user
     * ------------------------------------------------------
     * returns the released amount in ETH
     */ 
    function _calculateETHReleasedAmount(address _user) internal view returns(uint) {
        
        uint release = _users[_user].release;
        uint start = _users[_user].start;
        uint taken = _users[_user].wethWithdrawn;
        uint wethBonus = _users[_user].wethBonus;
        uint releasedPct;
        
        if (now >= release) releasedPct = 100;
        else releasedPct = ((now.sub(start)).mul(10000)).div((release.sub(start)).mul(100));
        
        uint released = (((wethBonus).mul(releasedPct)).div(100));
        return released.sub(taken);
    }
    
    /* 
     * @dev get the required fee for the released token bonus in the utility token
     * -------------------------------------------------------------------------------
     * param _user --> the address of the user
     * ----------------------------------------------------------
     * returns the fee amount in the utility token and Token
     */ 
    function _calculateTokenFee(uint _amount) internal view returns(uint uTokenFee, uint tokenFee) {
        
        uint fee = (_amount.mul(10)).div(100);
        uint feeInETH = _proportion(fee, address(token), address(weth));
        uint feeInUtoken = _proportion(feeInETH, address(weth), uToken1); 
        
        return (feeInUtoken, fee);
    }
    
    /* 
     * @dev get the required fee for the released ETH bonus in the utility token
     * -------------------------------------------------------------------------------
     * param _user --> the address of the user
     * ----------------------------------------------------------
     * returns the fee amount in the utility token and ETH
     */ 
    function _calculateETHFee(uint _amount) internal view returns(uint uTokenFee, uint ethFee) {
        
        uint fee = (_amount.mul(10)).div(100);
        uint feeInUtoken = _proportion(fee, address(weth), uToken1); 
        
        return (feeInUtoken, fee);
    }
    
    /* 
     * @dev get the required fee for the released ETH bonus   
     * -------------------------------------------------------------------------------
     * param _user --> the address of the user
     * ----------------------------------------------------------
     * returns the fee amount.
     */ 
    function calculateETHBonusFee(address _user) external view returns(uint ETH_Fee) {
        
        uint wethReleased = _calculateETHReleasedAmount(_user);
        
        if (wethReleased > 0) {
            
            (uint feeForWethInUtoken,) = _calculateETHFee(wethReleased);
            
            return feeForWethInUtoken;
            
        } else return 0;
    }
    
    /* 
     * @dev get the required fee for the released token bonus   
     * -------------------------------------------------------------------------------
     * param _user --> the address of the user
     * ----------------------------------------------------------
     * returns the fee amount.
     */ 
    function calculateTokenBonusFee(address _user) external view returns(uint token_Fee) {
        
        uint tokenReleased = _calculateTokenReleasedAmount(_user);
        
        if (tokenReleased > 0) {
            
            (uint feeForTokenInUtoken,) = _calculateTokenFee(tokenReleased);
            
            return feeForTokenInUtoken;
            
        } else return 0;
    }
    
    /* 
     * @dev get the bonus based on the return percentage for a particular locking term.   
     * -------------------------------------------------------------------------------
     * param _amount           --> the amount to calculate bonus from.
     * param _returnPercentage --> the returnPercentage of the term.
     * ----------------------------------------------------------
     * returns the bonus amount.
     */ 
    function _calculateBonus(uint _amount, uint _returnPercentage) internal pure returns(uint) {
        
        return ((_amount.mul(_returnPercentage)).div(100000)) / 2;                                  //  1% = 1000
    }
    
    /* 
     * @dev get the correct return percentage based on locked term.   
     * -----------------------------------------------------------
     * @param _token --> associated asset.
     * @param _term --> the locking term.
     * ----------------------------------------------------------
     * returns the return percentage.
     */   
    function _calculateReturnPercentage(IERC20 _token, uint _term) internal view returns(uint) {
        
        if (_token == weth) {
            if (_term == period1) return period1RPWeth;
            else if (_term == period2) return period2RPWeth;
            else if (_term == period3) return period3RPWeth;
            else if (_term == period4) return period4RPWeth;
            else return 0;
            
        } else if (_token == token) {
            if (_term == period1) return period1RPToken;
            else if (_term == period2) return period2RPToken;
            else if (_term == period3) return period3RPToken;
            else if (_term == period4) return period4RPToken;
            else return 0;
        }
    }
    
    /* 
     * @dev check whether the input locking term is one of the supported terms.  
     * ----------------------------------------------------------------------
     * @param _term --> the locking term.
     * --------------------------------------------------
     * returns whether true or not and the period's index
     */   
    function _isValidTerm(uint _term) internal view returns(bool isValid, uint Period) {
        
        if (_term == period1) return (true, 1);
        else if (_term == period2) return (true, 2);
        else if (_term == period3) return (true, 3);
        else if (_term == period4) return (true, 4);
        else return (false, 0);
    }
    
    /* 
     * @dev get the amount ETH and Token liquidity provided by the user.   
     * ------------------------------------------------------------------
     * @param _user --> the address of the user.
     * ---------------------------------------
     * returns the amount of ETH and Token liquidity provided.
     */   
    function getUserLiquidity(address _user) public view returns(uint provided_ETH, uint provided_token) {
        
        uint total = lpToken.totalSupply();
        uint ratio = ((_users[_user].liquidity).mul(scalar)).div(total);
        uint tokenHeld = token.balanceOf(address(lpToken));
        uint wethHeld = weth.balanceOf(address(lpToken));
        
        return ((ratio.mul(wethHeld)).div(scalar), (ratio.mul(tokenHeld)).div(scalar));
    }
    
    /* 
     * @dev check whether the inputted user address has been migrated.  
     * ----------------------------------------------------------------------
     * @param _user --> ddress of the user
     * ---------------------------------------
     * returns whether true or not.
     */  
    function getUserMigration(address _user) public view returns (bool) {
        
        return _users[_user].migrated;
    }
    
    /* 
     * @dev check whether the inputted user token has currently offers bonus  
     * ----------------------------------------------------------------------
     * @param _token --> associated token
     * ---------------------------------------
     * returns whether true or not.
     */  
    function _offersBonus(IERC20 _token) internal view returns (bool) {
        
        if (_token == weth) {
            uint wethRPTotal = period1RPWeth.add(period2RPWeth).add(period3RPWeth).add(period4RPWeth);
            if (wethRPTotal > 0) return true; 
            else return false;
            
        } else if (_token == token) {
            uint tokenRPTotal = period1RPToken.add(period2RPToken).add(period3RPToken).add(period4RPToken);
            if (tokenRPTotal > 0) return true;
            else return false;
        }
    }
    
    /* 
     * @dev check whether the pool has sufficient amount of bonuses available for new deposits/stakes.   
     * ----------------------------------------------------------------------------------------------
     * @param amount --> the _amount to be evaluated against.
     * ---------------------------------------------------
     * returns whether true or not.
     */ 
    function _checkForSufficientStakingBonusesForETH(uint _amount) internal view returns(bool) {
        
        if ((address(this).balance).sub(_pendingBonusesWeth) >= _amount) {
            return true;
        } else {
            return false;
        }
    }
    
    /* 
     * @dev check whether the pool has sufficient amount of bonuses available for new deposits/stakes.   
     * ----------------------------------------------------------------------------------------------
     * @param amount --> the _amount to be evaluated against.
     * ---------------------------------------------------
     * returns whether true or not.
     */ 
    function _checkForSufficientStakingBonusesForToken(uint _amount) internal view returns(bool) {
       
        if ((token.balanceOf(address(this)).sub(_pendingBonusesToken)) >= _amount) {
            
            return true;
            
        } else {
            
            return false;
        }
    }
    
    /* 
     * @dev get the timestamp of when the user balance will be released from locked term. 
     * ---------------------------------------------------------------------------------
     * @param _user --> the address of the user.
     * ---------------------------------------
     * returns the timestamp for the release.
     */     
    function getUserRelease(address _user) external view returns(uint release_time) {
        
        uint release = _users[_user].release;
        if (release > now) {
            
            return (release.sub(now));
       
        } else {
            
            return 0;
        }
        
    }
    
    /* 
     * @dev get the amount of bonuses rewarded from staking to a user.   
     * --------------------------------------------------------------
     * @param _user --> the address of the user.
     * ---------------------------------------
     * returns the amount of staking bonuses.
     */  
    function getUserBonusPending(address _user) public view returns(uint ETH_bonus, uint token_bonus) {
        
        uint takenWeth = _users[_user].wethWithdrawn;
        uint takenToken = _users[_user].tokenWithdrawn;
        
        return (_users[_user].wethBonus.sub(takenWeth), _users[_user].tokenBonus.sub(takenToken));
    }
    
    /* 
     * @dev get the amount of released bonuses rewarded from staking to a user.   
     * --------------------------------------------------------------
     * @param _user --> the address of the user.
     * ---------------------------------------
     * returns the amount of released staking bonuses.
     */  
    function getUserBonusAvailable(address _user) public view returns(uint ETH_Released, uint token_Released) {
        
        uint ETHValue = _calculateETHReleasedAmount(_user);
        uint tokenValue = _calculateTokenReleasedAmount(_user);
        
        return (ETHValue, tokenValue);
    }   
    
    /* 
     * @dev get the amount of liquidity pool tokens staked/locked by user.   
     * ------------------------------------------------------------------
     * @param _user --> the address of the user.
     * ---------------------------------------
     * returns the amount of locked liquidity.
     */   
    function getUserLPTokens(address _user) external view returns(uint user_LP) {

        return _users[_user].liquidity;
    }
    
    /* 
     * @dev get the lp token address for the pair.  
     * -----------------------------------------------------------
     * returns the lp address for eth/token pair.
     */ 
    function getLPAddress() external view returns(address) {
        
        return address(lpToken);
    }
    
    /* 
     * @dev get the total amount of LP tokens in the Poolstake contract
     * ----------------------------------------------------------------
     * returns the amount of LP tokens in the Poolstake contract
     */ 
    function getTotalLPTokens() external view returns(uint) {
        
        return lpToken.balanceOf(address(this));
    }
    
    /* 
     * @dev get the amount of staking bonuses available in the pool.  
     * -----------------------------------------------------------
     * returns the amount of staking bounses available for ETH and Token.
     */ 
    function getAvailableBonus() external view returns(uint available_ETH, uint available_token) {
        
        available_ETH = (address(this).balance).sub(_pendingBonusesWeth);
        available_token = (token.balanceOf(address(this))).sub(_pendingBonusesToken);
        
        return (available_ETH, available_token);
    }