contract LiquidityProvider {
    bool private initialized;

    IContractRegistry private contractRegistry;
    IERC20 private bnt;
    IERC20 private vbnt;

    address private xbnt;
    uint256 public nextDepositIndexToClaimBalance;

    function initializeAndAddLiquidity(
        IContractRegistry _contractRegistry,
        address _xbnt,
        IERC20 _bnt,
        IERC20 _vbnt,
        address _poolToken,
        uint256 _amount
    ) external returns(uint256) {
        require(msg.sender == _xbnt, 'Invalid caller');
        require(!initialized, 'Already initialized');
        initialized = true;

        contractRegistry = _contractRegistry;
        xbnt = _xbnt;
        bnt = _bnt;
        vbnt = _vbnt;

        return _addLiquidity(_poolToken, _amount);
    }

    function _addLiquidity(
        address _poolToken,
        uint256 _amount
    ) private returns(uint256 id) {
        ILiquidityProtection lp = getLiquidityProtectionContract();
        bnt.approve(address(lp), uint(-1));

        id = lp.addLiquidity(IConverterAnchor(_poolToken), bnt, _amount);

        _retrieveVbntBalance();
    }

    /*
     * @notice Restake this proxy's rewards
     */
    function claimAndRestake(address _poolToken) external onlyXbntContract returns(uint256 newDepositId, uint256 restakedBal){
        (, newDepositId) = getStakingRewardsContract().stakeRewards(uint(-1), IDSToken(_poolToken));
        restakedBal = _retrieveVbntBalance();
    }

    function claimRewards() external onlyXbntContract returns(uint256 rewardsAmount){
        rewardsAmount = _claimRewards();
    }

    function _claimRewards() private returns(uint256 rewards){
        rewards = getStakingRewardsContract().claimRewards();
        _retrieveBntBalance();
    }

    function _removeLiquidity(ILiquidityProtection _lp, uint256 _id) private {
        _lp.removeLiquidity(_id, 1000000); // full PPM resolution
    }

    /*
     * @notice Initiate final exit from this proxy
     */
    function claimRewardsAndRemoveLiquidity() external onlyXbntContract returns(uint256 rewards) {
        rewards = _claimRewards();
        uint256[] memory depositIds = getDepositIds();

        ILiquidityProtection lp = getLiquidityProtectionContract();
        vbnt.approve(address(lp), uint(-1));

        for(uint256 i = 0; i < depositIds.length; i++){
            _removeLiquidity(lp, depositIds[i]);
        }
    }

    /*
     * @notice Called 24 hours after `claimRewardsAndRemoveLiquidity`
     */
    function claimBalance() external onlyXbntContract {
        getLiquidityProtectionContract().claimBalance(0, getDepositIds().length);
        _retrieveBntBalance();
    }

    function _retrieveBntBalance() private {
        bnt.transfer(xbnt, bnt.balanceOf(address(this)));
    }

    function _retrieveVbntBalance() private returns(uint256 vbntBal) {
        vbntBal = vbnt.balanceOf(address(this));
        vbnt.transfer(xbnt, vbntBal);
    }

    function pendingRewards() external view returns(uint){
        return getStakingRewardsContract().pendingRewards(address(this));
    }

    function getStakingRewardsContract() private view returns(IStakingRewards){
        return IStakingRewards(contractRegistry.addressOf('StakingRewards'));
    }

    function getLiquidityProtectionContract() private view returns(ILiquidityProtection){
        return ILiquidityProtection(contractRegistry.addressOf('LiquidityProtection'));
    }

    function getDepositIds() private view returns(uint256[] memory){
        return IxBNT(xbnt).getProxyAddressDepositIds(address(this));
    }

    modifier onlyXbntContract {
        require(msg.sender == xbnt, 'Invalid caller');
        _;
    }
}
function claimBalance() external onlyXbntContract {
    getLiquidityProtectionContract().claimBalance(0, getDepositIds().length);
    _retrieveBntBalance();
}

/*
 * @notice Called 24 hours after `claimRewardsAndRemoveLiquidity`
 */