contract BancorV2_ZapIn_General_V1_1 is ReentrancyGuard, Ownable {
    using SafeMath for uint256;
    using Address for address;
    using SafeERC20 for IERC20;

    bool public stopped = false;
    uint16 public goodwill;

    address
        private constant ETHAddress = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    address
        private constant wethTokenAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address payable
        private constant zgoodwillAddress = 0x3CE37278de6388532C3949ce4e886F365B14fB56;

    bytes32 private constant liquidityProtectionName = "LiquidityProtection";
    bytes32
        private constant liquidityProtectionStoreName = "LiquidityProtectionStore";

    IBancorContractRegistry
        public constant bancorRegistry = IBancorContractRegistry(
        0x52Ae12ABe5D8BD778BD5397F99cA900624CfADD4
    );

    event Zapin(
        address toWhomToIssue,
        address toPoolAnchor,
        uint256 LPTRec,
        uint256 id
    );

    constructor(uint16 _goodwill) public {
        goodwill = _goodwill;
    }

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

    /**
    @notice This function is used to add single-sided protected liquidity to BancorV2 Pool
    @dev This function stakes LPT Received so the 
    @param _fromTokenAddress The token used for investment (address(0x00) if ether)
    @param _toBanConverterAddress The address of pool to add liquidity to
    @param _toReserveTokenAddress The Reserve to add liquidity to
    @param _amount The amount of ERC to invest
    @param _minPoolTokens received (minimum: 1)
    @param _allowanceTarget Spender for the swap
    @param _swapTarget Excecution target for the swap
    @param swapData DEX quote data
    @return LPT Received
     */
    function ZapInSingleSided(
        address _fromTokenAddress,
        address _toBanConverterAddress,
        address _toReserveTokenAddress,
        uint256 _amount,
        uint256 _minPoolTokens,
        address _allowanceTarget,
        address _swapTarget,
        bytes calldata swapData
    )
        external
        payable
        nonReentrant
        stopInEmergency
        returns (uint256 lptReceived, uint256 id)
    {
        uint256 valueToSend;
        address tokenToSend;
        address poolAnchor = IBancorV2Converter(_toBanConverterAddress)
            .anchor();

        if (_fromTokenAddress == address(0)) {
            require(msg.value > 0, "ERR: No ETH sent");
            valueToSend = _transferGoodwill(_fromTokenAddress, msg.value);
        } else {
            require(_amount > 0, "Err: No Tokens Sent");
            require(msg.value == 0, "ERR: ETH sent with Token");
            tokenToSend = _fromTokenAddress;
            IERC20(tokenToSend).safeTransferFrom(
                msg.sender,
                address(this),
                _amount
            );
            valueToSend = _transferGoodwill(_fromTokenAddress, _amount);
        }

        if (_fromTokenAddress == _toReserveTokenAddress) {
            (lptReceived, id) = _enterBancor(
                poolAnchor,
                tokenToSend,
                valueToSend
            );
        } else {
            uint256 reserveTokensBought = _fillQuote(
                tokenToSend,
                _toReserveTokenAddress,
                _amount,
                _allowanceTarget,
                _swapTarget,
                swapData
            );
            (lptReceived, id) = _enterBancor(
                poolAnchor,
                _toReserveTokenAddress,
                reserveTokensBought
            );
        }

        require(lptReceived >= _minPoolTokens, "ERR: High Slippage");

        emit Zapin(msg.sender, poolAnchor, lptReceived, id);
    }

    function _enterBancor(
        address _poolAnchor,
        address _reserveToken,
        uint256 _amount
    ) internal returns (uint256 lptReceived, uint256 id) {
        uint256 valueToSend;
        address reserveToken = _reserveToken;
        address bancorLiquidityProtectionAddress = bancorRegistry.addressOf(
            liquidityProtectionName
        );

        if (_reserveToken != address(0)) {
            IERC20 toReserveToken = IERC20(_reserveToken);
            toReserveToken.safeApprove(bancorLiquidityProtectionAddress, 0);
            toReserveToken.safeApprove(
                bancorLiquidityProtectionAddress,
                _amount
            );
        } else {
            valueToSend = _amount;
            reserveToken = ETHAddress;
        }


            IBancorLiquidityProtection bancorLiquidityProtection
         = IBancorLiquidityProtection(bancorLiquidityProtectionAddress);

        id = bancorLiquidityProtection.addLiquidity.value(valueToSend)(
            _poolAnchor,
            reserveToken,
            _amount
        );


            IBancorLiquidityProtectionStore liquidityProtectionStore
         = IBancorLiquidityProtectionStore(
            bancorRegistry.addressOf(liquidityProtectionStoreName)
        );

        (, , , lptReceived, , , , ) = liquidityProtectionStore
            .protectedLiquidity(id);

        id = bancorLiquidityProtection.transferLiquidity(id, msg.sender);
    }

    function _fillQuote(
        address _fromTokenAddress,
        address _toReserveTokenAddress,
        uint256 _amount,
        address _allowanceTarget,
        address _swapTarget,
        bytes memory swapData
    ) internal returns (uint256) {
        uint256 valueToSend;
        if (_fromTokenAddress == address(0)) {
            valueToSend = msg.value;
        } else {
            IERC20 fromToken = IERC20(_fromTokenAddress);
            fromToken.safeApprove(address(_allowanceTarget), 0);
            fromToken.safeApprove(address(_allowanceTarget), _amount);
        }

        uint256 initialBalance = _toReserveTokenAddress == address(0)
            ? address(this).balance
            : IERC20(_toReserveTokenAddress).balanceOf(address(this));

        (bool success, ) = _swapTarget.call.value(valueToSend)(swapData);
        require(success, "Error Swapping Tokens");

        uint256 finalBalance = _toReserveTokenAddress == address(0)
            ? (address(this).balance).sub(initialBalance)
            : IERC20(_toReserveTokenAddress).balanceOf(address(this)).sub(
                initialBalance
            );

        require(finalBalance > 0, "Swapped to Invalid Intermediate");

        return finalBalance;
    }

    /**
    @dev This function is used to calculate and transfer goodwill
    @param _tokenContractAddress Token from which goodwill is deducted
    @param valueToSend The total value being zapped in
    @return The quantity of remaining tokens
     */
    function _transferGoodwill(
        address _tokenContractAddress,
        uint256 valueToSend
    ) internal returns (uint256) {
        if (goodwill == 0) return valueToSend;

        uint256 goodwillPortion = SafeMath.div(
            SafeMath.mul(valueToSend, goodwill),
            10000
        );
        if (_tokenContractAddress == address(0)) {
            zgoodwillAddress.transfer(goodwillPortion);
        } else {
            IERC20(_tokenContractAddress).safeTransfer(
                zgoodwillAddress,
                goodwillPortion
            );
        }
        return valueToSend.sub(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(address _tokenAddress) public onlyOwner {
        IERC20 tokenAddress = IERC20(_tokenAddress);
        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 _transferGoodwill(
    address _tokenContractAddress,
    uint256 valueToSend
) internal returns (uint256) {
    if (goodwill == 0) return valueToSend;

    uint256 goodwillPortion = SafeMath.div(
        SafeMath.mul(valueToSend, goodwill),
        10000
    );
    if (_tokenContractAddress == address(0)) {
        zgoodwillAddress.transfer(goodwillPortion);
    } else {
        IERC20(_tokenContractAddress).safeTransfer(
            zgoodwillAddress,
            goodwillPortion
        );
    }
    return valueToSend.sub(goodwillPortion);
}

/**
 @dev This function is used to calculate and transfer goodwill
 @param _tokenContractAddress Token from which goodwill is deducted
 @param valueToSend The total value being zapped in
 @return The quantity of remaining tokens
 */