contract Unipool_Bridge_Zap_V1 is ReentrancyGuard, Ownable {
    using SafeMath for uint256;
    bool private stopped = false;
    uint16 public goodwill;
    address public dzgoodwillAddress;
    IuniswapFactory_Unipool_Bridge_Zap_V1 public UniSwapFactoryAddress = IuniswapFactory_Unipool_Bridge_Zap_V1(
        0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95
    );

    constructor(uint16 _goodwill, address payable _dzgoodwillAddress) public {
        goodwill = _goodwill;
        dzgoodwillAddress = _dzgoodwillAddress;
    }

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

    function set_new_UniSwapFactoryAddress(address _new_UniSwapFactoryAddress)
        public
        onlyOwner
    {
        UniSwapFactoryAddress = IuniswapFactory_Unipool_Bridge_Zap_V1(
            _new_UniSwapFactoryAddress
        );
    }

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

    function set_new_dzgoodwillAddress(address _new_dzgoodwillAddress)
        public
        onlyOwner
    {
        dzgoodwillAddress = _new_dzgoodwillAddress;
    }

    function ZapBridge(
        address _toWhomToIssue,
        address _FromTokenContractAddress,
        address _ToTokenContractAddress,
        uint256 _IncomingLP
    ) public payable nonReentrant stopInEmergency returns (bool) {
        require(
            _FromTokenContractAddress != _ToTokenContractAddress,
            "Cannot bridge to same pool"
        );

        uint256 goodwillPortion = SafeMath.div(
            SafeMath.mul(_IncomingLP, goodwill),
            10000
        );

        (uint256 ethReceived, uint256 erc20received) = _exitFromPool(
            _IncomingLP,
            goodwillPortion,
            _FromTokenContractAddress
        );

        (uint256 LiquidityTokens, uint256 eth4Token) = _enterToPool(
            erc20received,
            _FromTokenContractAddress,
            _ToTokenContractAddress
        );

        _transfer(
            LiquidityTokens,
            ethReceived.sub(eth4Token),
            _toWhomToIssue,
            _ToTokenContractAddress
        );
        return true;
    }

    function _enterToPool(
        uint256 erc20received,
        address _FromTokenContractAddress,
        address _ToTokenContractAddress
    ) internal returns (uint256 LiquidityTokens, uint256 eth4Token) {

            Iuniswap_Unipool_Bridge_Zap_V1 FromUniSwapExchangeContractAddress
         = Iuniswap_Unipool_Bridge_Zap_V1(
            UniSwapFactoryAddress.getExchange(_FromTokenContractAddress)
        );


            Iuniswap_Unipool_Bridge_Zap_V1 ToUniSwapExchangeContractAddress
         = Iuniswap_Unipool_Bridge_Zap_V1(
            UniSwapFactoryAddress.getExchange(_ToTokenContractAddress)
        );

        IERC20(_FromTokenContractAddress).approve(
            address(FromUniSwapExchangeContractAddress),
            erc20received
        );

        uint256 erc20bought = FromUniSwapExchangeContractAddress
            .tokenToTokenSwapInput(
            erc20received,
            1,
            1,
            SafeMath.add(now, 1800),
            _ToTokenContractAddress
        );

        require(erc20bought > 0, "Error in swapping ERC");

        IERC20(_ToTokenContractAddress).approve(
            address(ToUniSwapExchangeContractAddress),
            erc20bought.mul(2)
        );

        eth4Token = ToUniSwapExchangeContractAddress.getTokenToEthInputPrice(
            erc20bought
        );
        LiquidityTokens = ToUniSwapExchangeContractAddress.addLiquidity.value(
            eth4Token
        )(1, erc20bought, SafeMath.add(now, 1800));
        require(LiquidityTokens > 0, "Error in acquiring LP");
    }

    function _exitFromPool(
        uint256 _IncomingLP,
        uint256 goodwillPortion,
        address _FromTokenContractAddress
    ) internal returns (uint256 ethReceived, uint256 erc20received) {

            Iuniswap_Unipool_Bridge_Zap_V1 FromUniSwapExchangeContractAddress
         = Iuniswap_Unipool_Bridge_Zap_V1(
            UniSwapFactoryAddress.getExchange(_FromTokenContractAddress)
        );
        require(
            FromUniSwapExchangeContractAddress.transferFrom(
                msg.sender,
                address(this),
                SafeMath.sub(_IncomingLP, goodwillPortion)
            ),
            "Error in transferring LP:1"
        );
        require(
            FromUniSwapExchangeContractAddress.transferFrom(
                msg.sender,
                dzgoodwillAddress,
                goodwillPortion
            ),
            "Error in transferring LP:2"
        );

        (ethReceived, erc20received) = FromUniSwapExchangeContractAddress
            .removeLiquidity(
            SafeMath.sub(_IncomingLP, goodwillPortion),
            1,
            1,
            SafeMath.add(now, 1800)
        );
    }

    function _transfer(
        uint256 _LiquidityTokens,
        uint256 _residualEth,
        address _toWhomToIssue,
        address _ToTokenContractAddress
    ) internal {

            Iuniswap_Unipool_Bridge_Zap_V1 ToUniSwapExchangeContractAddress
         = Iuniswap_Unipool_Bridge_Zap_V1(
            UniSwapFactoryAddress.getExchange(_ToTokenContractAddress)
        );
        IERC20 ToTokenContractAddress = IERC20(_ToTokenContractAddress);
        require(
            ToUniSwapExchangeContractAddress.transfer(
                _toWhomToIssue,
                _LiquidityTokens
            ),
            "Error in transferring LP:3"
        );
        uint256 residualTokens = ToTokenContractAddress.balanceOf(
            address(this)
        );
        require(
            ToTokenContractAddress.transfer(_toWhomToIssue, residualTokens),
            "Error in returning residual tokens"
        );
        (bool success, ) = _toWhomToIssue.call.value(_residualEth)("");
        require(success, "Error in returning residual tokens");
    }

    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 {
        _owner.transfer(address(this).balance);
    }

    // - to kill the contract
    function destruct() public onlyOwner {
        selfdestruct(_owner);
    }

    function() external payable {}
}
function toggleContractActive() public onlyOwner {
    stopped = !stopped;
}

// - to Pause the contract