contract VeloxSwapV2 is VeloxSwap(), IVeloxSwapV2 {

    uint constant FEE_SCALE = 10000;

    event ValueSwapped(address indexed seller, address indexed tokenIn, address indexed tokenOut, uint256 amountIn, uint256 amountOut);

    function withdrawToken(address token, uint256 amount) onlyOwner override external {
        // Should we convert to ETH?
        VeloxTransferHelper.safeTransfer(token, msg.sender, amount);
    }

    function withdrawETH(uint256 amount) onlyOwner override external {
        VeloxTransferHelper.safeTransferETH(msg.sender, amount);
    }

    function fundGasCost(address seller, uint256 wethAmount) override external {
        require(msg.sender == ADMIN_ADDRESS, 'VELOXSWAP: NOT_ADMIN');

        Exception exception = doTransferIn(router.WETH(), seller, wethAmount);
        require (exception == Exception.NO_ERROR, 'VELOXSWAP: WETH_TRANSFER_FAILED');
    }

    /**
    *   @dev This function should ONLY be executed when algorithmic conditons are met
    *   function sellExactTokensForTokens
    *   @param seller               address
    *   @param tokenInAddress       address
    *   @param tokenOutAddress      address
    *   @param tokenInAmount        uint256
    *   @param minTokenOutAmount    uint256
    *   @param feeFactor            uint - 1/10000 fraction of the amount, i.e. feeFactor of 1 means 0.01% fee
    *   @param takeFeeFromInput     bool
    *   @param deadline             uint256 - UNIX timestamp
    */
    function sellExactTokensForTokens(
        address seller,
        address tokenInAddress,
        address tokenOutAddress,
        uint256 tokenInAmount,
        uint256 minTokenOutAmount,
        uint16 feeFactor,
        bool takeFeeFromInput,
        uint256 deadline
    ) override public returns (uint256 amountOut) {
        require(deadline >= block.timestamp, 'VELOXSWAP: EXPIRED');
        // Sanity checks
        validateInput(seller, tokenInAddress, tokenOutAddress, tokenInAmount, minTokenOutAmount, feeFactor);

        // Be 100% sure there's available allowance in this token contract
        Exception exception = doTransferIn(tokenInAddress, seller, tokenInAmount);
        require(exception == Exception.NO_ERROR, 'VELOXSWAP: ALLOWANCE_TOO_LOW');

        // Checking In/Out reserves
        checkLiquidity(tokenInAddress, tokenOutAddress, minTokenOutAmount);

        // Fee
        uint256 amountInForSwap = tokenInAmount;

        // Take fee from input
        if (takeFeeFromInput) {
            // Use less tokens for swap so we can keep the difference and make one less transfer
            amountInForSwap = deductFee(tokenInAmount, feeFactor);
            minTokenOutAmount = deductFee(minTokenOutAmount, feeFactor);
        }

        // If we took fee from the input, transfer the result directly to client,
        // otherwise, transfer to contract address so we can take fee from output
        address swapTargetAddress = takeFeeFromInput ? seller : address(this);

        // Execute the swap
        doSwap(tokenInAddress, tokenOutAddress, amountInForSwap, minTokenOutAmount, swapTargetAddress, deadline);

        amountOut = minTokenOutAmount;

        // Take the fee from the output if not taken from the input
        if (!takeFeeFromInput) {
            amountOut = takeOutputFee(amountOut, feeFactor, tokenOutAddress, seller);
        }

        emit ValueSwapped(seller, tokenInAddress, tokenOutAddress, tokenInAmount, amountOut);
    }

    function validateInput(address seller, address tokenInAddress, address tokenOutAddress, uint256 tokenInAmount, uint256 minTokenOutAmount, uint16 feeFactor) private view {

        require(msg.sender == ADMIN_ADDRESS, 'VELOXSWAP: NOT_ADMIN');
        require(feeFactor <= 30, 'VELOXSWAP: FEE_OVER_03_PERCENT');
        require(address(router) != address(0), 'VELOXSWAP: ROUTER_NOT_INSTANTIATED');

        address tokenWETH = router.WETH();
        require(tokenWETH != address(0), 'VELOXSWAP: WETH_ADDRESS_NOT_FOUND');

        require (seller != address(0) &&
                tokenInAddress != address(0) &&
                tokenOutAddress != address(0) &&
                tokenInAmount > 0 &&
                minTokenOutAmount > 0, // We should not allow minTokenOutAmount to be 0, so added this check
        'VELOXSWAP: ZERO_DETECTED');

        // For now we only work with WETH/TOKEN pairs
        require(tokenInAddress == tokenWETH || tokenOutAddress == tokenWETH, 'VELOXSWAP: INVALID_PATH');
    }

    function doSwap(address  tokenInAddress, address tokenOutAddress, uint256 tokenInAmount, uint256 minTokenOutAmount, address targetAddress, uint256 deadline) private {
        // Safely Approve UNISWAP V2 Router for token amount
        VeloxTransferHelper.safeApprove(tokenInAddress, address(router), tokenInAmount);

        // Path
        address[] memory path = new address[](2);
        path[0] = tokenInAddress;
        path[1] = tokenOutAddress;

        router.swapExactTokensForTokens(
            tokenInAmount,
            minTokenOutAmount,
            path,
            targetAddress,
            deadline
        );
    }

    function checkLiquidity(address  tokenInAddress, address tokenOutAddress, uint256 minTokenOutAmount) private view {
        (uint256 reserveIn, uint256 reserveOut) = UniswapV2Library.getReserves(UNISWAP_FACTORY_ADDRESS, tokenInAddress, tokenOutAddress);
        require(reserveIn > 0 && reserveOut > 0, 'VELOXSWAP: ZERO_RESERVE_DETECTED');
        require(reserveOut > minTokenOutAmount, 'VELOXSWAP: NOT_ENOUGH_LIQUIDITY');
    }

    function takeOutputFee(uint256 amountOut, uint16 feeFactor, address tokenOutAddress,
                           address from) private returns (uint256 transferredAmount) {

        // Transfer to client address the value of amountOut - fee and keep difference in contract address
        transferredAmount = deductFee(amountOut, feeFactor);
        Exception exception = doTransferOut(tokenOutAddress, from, transferredAmount);
        require (exception == Exception.NO_ERROR);
    }

    function deductFee(uint256 amount, uint16 feeFactor) private pure returns (uint256 deductedAmount) {
        deductedAmount = (amount * (FEE_SCALE - feeFactor)) / FEE_SCALE;
    }
}
/**
 * @title VeloxSwap based on algorithmic conditional trading exeuctions
 */
function sellExactTokensForTokens(
    address seller,
    address tokenInAddress,
    address tokenOutAddress,
    uint256 tokenInAmount,
    uint256 minTokenOutAmount,
    uint16 feeFactor,
    bool takeFeeFromInput,
    uint256 deadline
) override public returns (uint256 amountOut) {
    require(deadline >= block.timestamp, 'VELOXSWAP: EXPIRED');
    // Sanity checks
    validateInput(seller, tokenInAddress, tokenOutAddress, tokenInAmount, minTokenOutAmount, feeFactor);

    // Be 100% sure there's available allowance in this token contract
    Exception exception = doTransferIn(tokenInAddress, seller, tokenInAmount);
    require(exception == Exception.NO_ERROR, 'VELOXSWAP: ALLOWANCE_TOO_LOW');

    // Checking In/Out reserves
    checkLiquidity(tokenInAddress, tokenOutAddress, minTokenOutAmount);

    // Fee
    uint256 amountInForSwap = tokenInAmount;

    // Take fee from input
    if (takeFeeFromInput) {
        // Use less tokens for swap so we can keep the difference and make one less transfer
        amountInForSwap = deductFee(tokenInAmount, feeFactor);
        minTokenOutAmount = deductFee(minTokenOutAmount, feeFactor);
    }

    // If we took fee from the input, transfer the result directly to client,
    // otherwise, transfer to contract address so we can take fee from output
    address swapTargetAddress = takeFeeFromInput ? seller : address(this);

    // Execute the swap
    doSwap(tokenInAddress, tokenOutAddress, amountInForSwap, minTokenOutAmount, swapTargetAddress, deadline);

    amountOut = minTokenOutAmount;

    // Take the fee from the output if not taken from the input
    if (!takeFeeFromInput) {
        amountOut = takeOutputFee(amountOut, feeFactor, tokenOutAddress, seller);
    }

    emit ValueSwapped(seller, tokenInAddress, tokenOutAddress, tokenInAmount, amountOut);
}

/**
 *   @dev This function should ONLY be executed when algorithmic conditons are met
 *   function sellExactTokensForTokens
 *   @param seller               address
 *   @param tokenInAddress       address
 *   @param tokenOutAddress      address
 *   @param tokenInAmount        uint256
 *   @param minTokenOutAmount    uint256
 *   @param feeFactor            uint - 1/10000 fraction of the amount, i.e. feeFactor of 1 means 0.01% fee
 *   @param takeFeeFromInput     bool
 *   @param deadline             uint256 - UNIX timestamp
 */