contract Balancer_UniswapV2_Pipe_V1_4 is ReentrancyGuard, Ownable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;
    using Address for address;
    bool public stopped = false;

    IUniswapV2Factory
        private constant UniSwapV2FactoryAddress = IUniswapV2Factory(
        0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f
    );

    IBalancerZapOut public balancerZapOut;
    IUniswapV2ZapIn public uniswapV2ZapIn;
    IBalancerZapIn public balancerZapIn;
    IUniswapV2ZapOut public uniswapV2ZapOut;

    constructor(
        address _balancerZapIn,
        address _balancerZapOut,
        address _uniswapV2ZapIn,
        address _uniswapV2ZapOut
    ) public {
        balancerZapIn = IBalancerZapIn(_balancerZapIn);
        balancerZapOut = IBalancerZapOut(_balancerZapOut);
        uniswapV2ZapIn = IUniswapV2ZapIn(_uniswapV2ZapIn);
        uniswapV2ZapOut = IUniswapV2ZapOut(_uniswapV2ZapOut);
    }

    // circuit breaker modifiers
    modifier stopInEmergency {
        if (stopped) {
            revert("Temporarily Paused");
        } else {
            _;
        }
    }

    function PipeBalancerUniV2(
        address _FromBalancerPoolAddress,
        uint256 _IncomingBPT,
        address _toUniswapPoolAddress,
        address _toWhomToIssue,
        uint256 _minUniV2Tokens
    ) public nonReentrant stopInEmergency returns (uint256) {
        // Get BPT
        IERC20(_FromBalancerPoolAddress).safeTransferFrom(
            msg.sender,
            address(this),
            _IncomingBPT
        );
        // Approve BalUnZap
        IERC20(_FromBalancerPoolAddress).safeApprove(
            address(balancerZapOut),
            _IncomingBPT
        );

        // Get pair addresses from UniV2Pair
        address token0 = IUniswapV2Pair(_toUniswapPoolAddress).token0();
        address token1 = IUniswapV2Pair(_toUniswapPoolAddress).token1();

        address zapOutToToken = address(0);
        if (IBPool(_FromBalancerPoolAddress).isBound(token0)) {
            zapOutToToken = token0;
        } else if (IBPool(_FromBalancerPoolAddress).isBound(token1)) {
            zapOutToToken = token1;
        }

        // ZapOut from Balancer
        uint256 zappedOutAmt = balancerZapOut.EasyZapOut(
            zapOutToToken,
            _FromBalancerPoolAddress,
            _IncomingBPT,
            0
        );

        uint256 LPTBought;
        if (zapOutToToken == address(0)) {
            // use ETH to ZapIn to UNIV2
            LPTBought = uniswapV2ZapIn.ZapIn.value(zappedOutAmt)(
                _toWhomToIssue,
                address(0),
                token0,
                token1,
                0,
                _minUniV2Tokens
            );
        } else {
            IERC20(zapOutToToken).safeApprove(
                address(uniswapV2ZapIn),
                IERC20(zapOutToToken).balanceOf(address(this))
            );
            LPTBought = uniswapV2ZapIn.ZapIn.value(0)(
                _toWhomToIssue,
                zapOutToToken,
                token0,
                token1,
                zappedOutAmt,
                _minUniV2Tokens
            );
        }

        return LPTBought;
    }

    function PipeUniV2Balancer(
        address _FromUniswapPoolAddress,
        uint256 _IncomingLPT,
        address _ToBalancerPoolAddress,
        address _toWhomToIssue,
        uint256 _minBPTokens
    ) public nonReentrant stopInEmergency returns (uint256) {
        // Get LPT
        IERC20(_FromUniswapPoolAddress).safeTransferFrom(
            msg.sender,
            address(this),
            _IncomingLPT
        );

        // Approve UniUnZap
        IERC20(_FromUniswapPoolAddress).safeApprove(
            address(uniswapV2ZapOut),
            _IncomingLPT
        );

        // Get pair addresses from UniV2Pair
        address token0 = IUniswapV2Pair(_FromUniswapPoolAddress).token0();
        address token1 = IUniswapV2Pair(_FromUniswapPoolAddress).token1();

        address zapOutToToken = address(0);
        if (IBPool(_ToBalancerPoolAddress).isBound(token0)) {
            zapOutToToken = token0;
        } else if (IBPool(_ToBalancerPoolAddress).isBound(token1)) {
            zapOutToToken = token1;
        }

        // ZapOut from Uni
        uint256 tokensRec = uniswapV2ZapOut.ZapOut(
            zapOutToToken,
            _FromUniswapPoolAddress,
            _IncomingLPT,
            0
        );

        // ZapIn to Balancer
        uint256 BPTBought;
        if (zapOutToToken == address(0)) {
            // use ETH to ZapIn to Balancer
            BPTBought = balancerZapIn.ZapIn.value(tokensRec)(
                address(0),
                _ToBalancerPoolAddress,
                0,
                _minBPTokens
            );
        } else {
            IERC20(zapOutToToken).safeApprove(address(balancerZapIn), tokensRec);
            BPTBought = balancerZapIn.ZapIn.value(0)(
                zapOutToToken,
                _ToBalancerPoolAddress,
                tokensRec,
                _minBPTokens
            );
        }

        IERC20(_ToBalancerPoolAddress).safeTransfer(_toWhomToIssue, BPTBought);

        return BPTBought;
    }

    // Zap Contract Setters
    function setbalancerZapIn(address _balancerZapIn) public onlyOwner {
        balancerZapIn = IBalancerZapIn(_balancerZapIn);
    }

    function setBalancerZapOut(address _balancerZapOut) public onlyOwner {
        balancerZapOut = IBalancerZapOut(_balancerZapOut);
    }

    function setUniswapV2ZapIn(address _uniswapV2ZapIn) public onlyOwner {
        uniswapV2ZapIn = IUniswapV2ZapIn(_uniswapV2ZapIn);
    }

    function setUniswapV2ZapOut(address _uniswapV2ZapOut) public onlyOwner {
        uniswapV2ZapOut = IUniswapV2ZapOut(_uniswapV2ZapOut);
    }

    // fallback to receive ETH
    function() external payable {}

    function inCaseTokengetsStuck(IERC20 _TokenAddress) public onlyOwner {
        uint256 qty = _TokenAddress.balanceOf(address(this));
        _TokenAddress.safeTransfer(owner(), qty);
    }

    // - to Pause the contract
    function toggleContractActive() public onlyOwner {
        stopped = !stopped;
    }

    // - to withdraw any ETH balance sitting in the contract
    function withdraw() public onlyOwner {
        uint256 contractBalance = address(this).balance;
        address payable _to = owner().toPayable();
        Address.sendValue(_to, contractBalance);
    }
}
function toggleContractActive() public onlyOwner {
    stopped = !stopped;
}

// - to Pause the contract