contract UniswapV2_Pipe_V1_1 is ReentrancyGuard, Ownable {
    using SafeMath for uint256;
    using Address for address;
    using SafeERC20 for IERC20;
    bool private stopped = false;
    uint16 public goodwill;
    address
        private constant zgoodwillAddress = 0xE737b6AfEC2320f616297e59445b60a11e3eF75F;

    IUniV2ZapOut public uniV2ZapOut;
    IUniV2ZapIn public uniV2ZapIn;

    address private constant wethTokenAddress = address(
        0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
    );

    constructor(
        address _uniV2ZapInAddress,
        address _uniV2ZapOutAddress,
        uint16 _goodwill
    ) public {
        uniV2ZapIn = IUniV2ZapIn(_uniV2ZapInAddress);
        uniV2ZapOut = IUniV2ZapOut(_uniV2ZapOutAddress);
        goodwill = _goodwill;
    }

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

    function PipeUniV2(
        address payable _toWhomToIssue,
        address _incomingUniV2Exchange,
        uint256 _IncomingLPT,
        address _toUniV2Exchange,
        uint256 _minPoolTokens
    ) public nonReentrant stopInEmergency returns (uint256 lptReceived) {
        // transfer lpt
        IERC20(_incomingUniV2Exchange).safeTransferFrom(
            msg.sender,
            address(this),
            _IncomingLPT
        );

        // find all 4 tokens
        IUniswapV2Pair pair1 = IUniswapV2Pair(_incomingUniV2Exchange);
        IUniswapV2Pair pair2 = IUniswapV2Pair(_toUniV2Exchange);

        address token1_1 = pair1.token0();
        address token1_2 = pair1.token1();
        address token2_1 = pair2.token0();
        address token2_2 = pair2.token1();

        address intermediateToken = wethTokenAddress;

        // check for common token
        if (token1_1 == token2_1 || token1_1 == token2_2) {
            intermediateToken = token1_1;
        } else if (token1_2 == token2_1 || token1_2 == token2_2) {
            intermediateToken = token1_2;
        }

        // approve ZapOut contract
        IERC20(_incomingUniV2Exchange).safeApprove(
            address(uniV2ZapOut),
            _IncomingLPT
        );

        // zapout
        uint256 intermediateReceived = uniV2ZapOut.ZapOut(
            intermediateToken,
            _incomingUniV2Exchange,
            _IncomingLPT,
            1
        );

        // transfer goodwill portion
        uint256 goodwillPortion = _transferGoodwill(
            intermediateToken,
            intermediateReceived
        );

        // approve ZapIn
        IERC20(intermediateToken).safeApprove(
            address(uniV2ZapIn),
            intermediateReceived.sub(goodwillPortion)
        );

        // ZapIn with intermediate
        lptReceived = _performZapIn(
            _toWhomToIssue,
            intermediateToken,
            token2_1,
            token2_2,
            intermediateReceived.sub(goodwillPortion)
        );
        require(lptReceived >= _minPoolTokens, "Err: High Slippage");
    }

    function PipeUniV2WithPermit(
        address payable _toWhomToIssue,
        address _incomingUniV2Exchange,
        uint256 _IncomingLPT,
        address _toUniV2Exchange,
        uint256 _minPoolTokens,
        uint256 _approvalAmount,
        uint256 _deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256) {
        // permit
        IUniswapV2Pair(_incomingUniV2Exchange).permit(
            msg.sender,
            address(this),
            _approvalAmount,
            _deadline,
            v,
            r,
            s
        );

        return (
            PipeUniV2(
                _toWhomToIssue,
                _incomingUniV2Exchange,
                _IncomingLPT,
                _toUniV2Exchange,
                _minPoolTokens
            )
        );
    }

    function _performZapIn(
        address _toWhomToIssue,
        address intermediateToken,
        address token2_1,
        address token2_2,
        uint256 intermediateAmt
    ) internal returns (uint256 _lptRec) {
        return (
            uniV2ZapIn.ZapIn(
                _toWhomToIssue,
                intermediateToken,
                token2_1,
                token2_2,
                intermediateAmt,
                1
            )
        );
    }

    /**
    @notice This function is used to calculate and transfer goodwill
    @param _tokenContractAddress Token in which goodwill is deducted
    @param tokens2Trade The total amount of tokens to be zapped in
    @return goodwillPortion The quantity of goodwill deducted
     */
    function _transferGoodwill(
        address _tokenContractAddress,
        uint256 tokens2Trade
    ) internal returns (uint256 goodwillPortion) {
        goodwillPortion = SafeMath.div(
            SafeMath.mul(tokens2Trade, goodwill),
            10000
        );

        if (goodwillPortion == 0) {
            return 0;
        }

        IERC20(_tokenContractAddress).safeTransfer(
            zgoodwillAddress,
            goodwillPortion
        );
    }

    function set_new_goodwill(uint16 _new_goodwill) public onlyOwner {
        require(
            _new_goodwill >= 0 && _new_goodwill < 10000,
            "GoodWill Value not allowed"
        );
        goodwill = _new_goodwill;
    }

    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();
        _to.transfer(contractBalance);
    }

    function() external payable {
        require(msg.sender != tx.origin, "Do not send ETH directly");
    }
}
function _transferGoodwill(
    address _tokenContractAddress,
    uint256 tokens2Trade
) internal returns (uint256 goodwillPortion) {
    goodwillPortion = SafeMath.div(
        SafeMath.mul(tokens2Trade, goodwill),
        10000
    );

    if (goodwillPortion == 0) {
        return 0;
    }

    IERC20(_tokenContractAddress).safeTransfer(
        zgoodwillAddress,
        goodwillPortion
    );
}

/**
 @notice This function is used to calculate and transfer goodwill
 @param _tokenContractAddress Token in which goodwill is deducted
 @param tokens2Trade The total amount of tokens to be zapped in
 @return goodwillPortion The quantity of goodwill deducted
 */