contract Balancer_UniswapV2_Pipe_V1_1 is ReentrancyGuard, Ownable {
    using SafeMath for uint256;
    using Address for address;
    bool private stopped = false;

    IUniswapV2Factory
        private constant UniSwapV2FactoryAddress = IUniswapV2Factory(
        0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f
    );

    IBalancerUnZap public balancerUnZap;
    IUniswapV2ZapIn public uniZapIn;
    IBalancerZapInGen public balancerZapIn;
    IUniswapV2ZapOut public uniUnZap;

    constructor(
        address _balancerZapIn,
        address _balancerUnZap,
        address _uniZapIn,
        address _uniUnZap
    ) public {
        balancerUnZap = IBalancerUnZap(_balancerUnZap);
        uniZapIn = IUniswapV2ZapIn(_uniZapIn);
        balancerZapIn = IBalancerZapInGen(_balancerZapIn);
        uniUnZap = IUniswapV2ZapOut(_uniUnZap);
    }

    // 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).transferFrom(
            msg.sender,
            address(this),
            _IncomingBPT
        );
        // Approve BalUnZap
        IERC20(_FromBalancerPoolAddress).approve(
            address(balancerUnZap),
            _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 = balancerUnZap.EasyZapOut(
            zapOutToToken,
            _FromBalancerPoolAddress,
            _IncomingBPT,
            0
        );

        uint256 LPTBought;
        if (zapOutToToken == address(0)) {
            // use ETH to ZapIn to UNIV2
            LPTBought = uniZapIn.ZapIn.value(zappedOutAmt)(
                address(0),
                token0,
                token1,
                0,
                _minUniV2Tokens
            );
        } else {
            IERC20(zapOutToToken).approve(
                address(uniZapIn),
                IERC20(zapOutToToken).balanceOf(address(this))
            );
            LPTBought = uniZapIn.ZapIn.value(0)(
                zapOutToToken,
                token0,
                token1,
                zappedOutAmt,
                _minUniV2Tokens
            );
        }
        // Send LPT to user
        address _ToUniPoolAddress = UniSwapV2FactoryAddress.getPair(
            token0,
            token1
        );
        IERC20(_ToUniPoolAddress).transfer(_toWhomToIssue, LPTBought);

        return LPTBought;
    }

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

        // Approve UniUnZap
        IERC20(_FromUniswapPoolAddress).approve(
            address(uniUnZap),
            _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 = uniUnZap.ZapOut(
            zapOutToToken,
            _FromUniswapPoolAddress,
            _IncomingLPT,
            0
        );

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

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

        return BPTBought;
    }

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

    function inCaseTokengetsStuck(IERC20 _TokenAddress) public onlyOwner {
        uint256 qty = _TokenAddress.balanceOf(address(this));
        _TokenAddress.transfer(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();
        _to.transfer(contractBalance);
    }

    function updateZapAddresses(
        address _balancerUnZap,
        address _uniZapIn,
        address _balancerZapIn,
        address _uniUnZap
    ) public onlyOwner {
        balancerUnZap = IBalancerUnZap(_balancerUnZap);
        uniZapIn = IUniswapV2ZapIn(_uniZapIn);
        balancerZapIn = IBalancerZapInGen(_balancerZapIn);
        uniUnZap = IUniswapV2ZapOut(_uniUnZap);
    }
}
function() external payable {}

// fallback to receive ETH