contract ERC20toUniPoolZapV2_General is Initializable, Ownable {
    using SafeMath for uint256;

    // state variables

    // - THESE MUST ALWAYS STAY IN THE SAME LAYOUT
    bool private stopped = false;

    IuniswapFactory_ERC20toUniPoolZapV1 public UniSwapFactoryAddress = IuniswapFactory_ERC20toUniPoolZapV1(
        0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95
    );

    mapping(address => uint256) private userBalance;

    // events
    event details(
        address indexed user,
        address toWhomIssued,
        address indexed IncomingTokenAddress,
        address indexed UniPoolUnderlyingTokenAddressTokenAddress
    );
    event residualETH(uint256 residualETHtransferred);

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

    constructor() public {}

    function LetsInvest(
        address _toWhomToIssue,
        address _IncomingTokenContractAddress,
        uint256 _IncomingTokenQty,
        address _UniPoolsUnderlyingTokenAddress
    )
        public
        stopInEmergency
        returns (bool)
    {
        uint256 investmentQTY = _IncomingTokenQty;
        require(
            IERC20(_IncomingTokenContractAddress).transferFrom(
                msg.sender,
                address(this),
                investmentQTY
            ),
            "Error in transferring token:1"
        );
        require(
            (
                invest2UniPool(
                    investmentQTY,
                    _toWhomToIssue,
                    _IncomingTokenContractAddress,
                    _UniPoolsUnderlyingTokenAddress
                )
            ),
            "error in invest2UniPool"
        );
        emit details(
            msg.sender,
            _toWhomToIssue,
            _IncomingTokenContractAddress,
            _UniPoolsUnderlyingTokenAddress
        );
        return (true);
    }

    function invest2UniPool(
        uint256 _ERC20QTY,
        address _toWhomToIssue,
        address _IncomingTokenContractAddress,
        address _UniPoolsUnderlyingTokenAddress
    ) internal returns (bool) {
        // setting some internal variables
        uint256 EthOnConversion;
        uint256 nonConvertiblePortion;
        IuniswapExchange_ERC20toUniPoolZapV1 UniSwapExchangeContractAddress;
        // computation
        if (_IncomingTokenContractAddress == _UniPoolsUnderlyingTokenAddress) {
            nonConvertiblePortion = SafeMath.div(
                SafeMath.mul(_ERC20QTY, 503),
                1000
            );
            uint256 convertiblePortion = SafeMath.sub(
                _ERC20QTY,
                nonConvertiblePortion
            );
            // finding the uniswap address
            UniSwapExchangeContractAddress = IuniswapExchange_ERC20toUniPoolZapV1(
                UniSwapFactoryAddress.getExchange(_IncomingTokenContractAddress)
            );
            // approving the address
            IERC20(_IncomingTokenContractAddress).approve(
                address(UniSwapExchangeContractAddress),
                _ERC20QTY
            );
            // converting the required portion to ETH
            EthOnConversion = UniSwapExchangeContractAddress
                .tokenToEthSwapInput(
                convertiblePortion,
                1,
                SafeMath.add(now, 1800)
            );

        } else {
            // convert ERCs
            IuniswapExchange_ERC20toUniPoolZapV1 UniSwapExchangeContractAddress_incomingToken = IuniswapExchange_ERC20toUniPoolZapV1(
                UniSwapFactoryAddress.getExchange(_IncomingTokenContractAddress)
            );

            require(
                (
                    IERC20(_IncomingTokenContractAddress).approve(
                        address(UniSwapExchangeContractAddress_incomingToken),
                        _ERC20QTY
                    )
                ),
                "error in approval:3"
            );
            uint256 nonConvertiblePortion_beforeConversion = SafeMath.div(
                SafeMath.mul(_ERC20QTY, 503),
                1000
            );
            nonConvertiblePortion = UniSwapExchangeContractAddress_incomingToken
                .tokenToTokenSwapInput(
                nonConvertiblePortion_beforeConversion,
                1,
                1,
                SafeMath.add(now, 1800),
                _UniPoolsUnderlyingTokenAddress
            );

            uint256 convertiblePortion = SafeMath.sub(
                _ERC20QTY,
                nonConvertiblePortion_beforeConversion
            );

            EthOnConversion = UniSwapExchangeContractAddress_incomingToken
                .tokenToEthSwapInput(
                convertiblePortion,
                1,
                SafeMath.add(now, 1800)
            );

            require(
                (
                    IERC20(_IncomingTokenContractAddress).approve(
                        address(UniSwapExchangeContractAddress_incomingToken),
                        0
                    )
                ),
                "error in setting approval back to zero"
            );
            UniSwapExchangeContractAddress = IuniswapExchange_ERC20toUniPoolZapV1(
                UniSwapFactoryAddress.getExchange(
                    _UniPoolsUnderlyingTokenAddress
                )
            );

        }
        require(
            (
                addLiquidity(
                    _toWhomToIssue,
                    _UniPoolsUnderlyingTokenAddress,
                    nonConvertiblePortion,
                    UniSwapExchangeContractAddress,
                    EthOnConversion
                )
            ),
            "issue in adding Liquidity"
        );
        return (true);
    }

    function addLiquidity(
        address _toWhomToIssue,
        address _UniPoolsUnderlyingTokenAddress,
        uint256 _UsableERC20,
        IuniswapExchange_ERC20toUniPoolZapV1 _UniSwapExchangeContractAddress,
        uint256 _valueinETH
    ) internal returns (bool) {
        uint256 max_tokens_ans = getMaxTokens(
            address(_UniSwapExchangeContractAddress),
            IERC20(_UniPoolsUnderlyingTokenAddress),
            _valueinETH
        );

        require(
            (
                IERC20(_UniPoolsUnderlyingTokenAddress).approve(
                    address(_UniSwapExchangeContractAddress),
                    _UsableERC20
                )
            ),
            "error in approving the unicontract, addLiquidity"
        );
        uint256 LiquidityTokens = _UniSwapExchangeContractAddress
            .addLiquidity
            .value(_valueinETH)(1, max_tokens_ans, SafeMath.add(now, 1800));
        require(
            LiquidityTokens ==
                _UniSwapExchangeContractAddress.balanceOf(address(this)),
            "error3:DeFiZap"
        );
        require(
            _UniSwapExchangeContractAddress.transfer(
                _toWhomToIssue,
                LiquidityTokens
            ),
            "error6:DeFiZap"
        );

        // computing the remainder of the tokens
        uint256 residual = IERC20(_UniPoolsUnderlyingTokenAddress).balanceOf(
            address(this)
        );
        uint256 ETHfromResidual = _UniSwapExchangeContractAddress
            .tokenToEthTransferInput(
            residual,
            1,
            SafeMath.add(now, 1800),
            _toWhomToIssue
        );
        emit residualETH(ETHfromResidual);
        require(
            (
                IERC20(_UniPoolsUnderlyingTokenAddress).approve(
                    address(_UniSwapExchangeContractAddress),
                    0
                )
            ),
            "error in resetting the approval to zero"
        );
        return true;
    }

    function getMaxTokens(
        address _UniSwapExchangeContractAddress,
        IERC20 _ERC20TokenAddress,
        uint256 _value
    ) internal view returns (uint256) {
        uint256 contractBalance = address(_UniSwapExchangeContractAddress)
            .balance;
        uint256 eth_reserve = SafeMath.sub(contractBalance, _value);
        uint256 token_reserve = _ERC20TokenAddress.balanceOf(
            _UniSwapExchangeContractAddress
        );
        uint256 token_amount = SafeMath.div(
            SafeMath.mul(_value, token_reserve),
            eth_reserve
        ) +
            1;
        return token_amount;
    }

    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);
    }

    // - fallback function let you / anyone send ETH to this wallet without the need to call any function
    function() external payable {
    }
}
function toggleContractActive() public onlyOwner {
    stopped = !stopped;
}

// - to Pause the contract