contract SwapProxy is ISwapProxy, BaseProxy, ReentrancyGuard {
    using SafeERC20 for IERC20;
    using Address for address;
    using SafeMath for uint256;

    uint256 public constant SLIPPAGE_PRECISION = 10**4;

    event tokenSwapped(
        address indexed user,
        address indexed tokenIn,
        address indexed tokenOut,
        uint256 amountIn,
        uint256 amountOut
    );

    IUniswapV2Router02 public uniswapRouter;

    mapping(address => uint256) public userAgent;
    uint256 private deadline =
        0xf000000000000000000000000000000000000000000000000000000000000000;

    /**
     * @param uniswapRouterAddress: The uniswap router address for managing uniswap pool liquidity
     * @param agentManagerAddress: The agentManager address for managing user agents
     * @param actionFee: The action fee value, zero for no fees
     * @param feeDepositAddress: The fee deposit address
     */
    constructor(
        address uniswapRouterAddress,
        address agentManagerAddress,
        uint256 actionFee,
        address feeDepositAddress
    ) BaseProxy(agentManagerAddress, actionFee, feeDepositAddress) {
        uniswapRouter = IUniswapV2Router02(uniswapRouterAddress);
    }

    /**
     * @notice Function that swaps _tokenIn amount as much approved by the user to this contract for _tokenOut
     * If approval > _tokenIn balance, all of the _tokenIn amount/balance is swapped
     * @param agentId: unique id of the agent doing the transaction
     * @param user: user address to withdraw liquidity for
     * @param path: path for token swap
     * @param slippage: Slippage tolerance to consider
     */
    function swap(
        string calldata agentId,
        address user,
        address[] calldata path,
        uint256 amount,
        uint256 slippage
    ) external override nonReentrant agentVerified(agentId, user) returns (bool) {
        IERC20 tokenIn = IERC20(path[0]);

        uint256 approvedBalance = tokenIn.allowance(user, address(this));
        uint256 actualBalance = tokenIn.balanceOf(user);

        require(
            actualBalance > 0 && approvedBalance > 0,
            "User has no tokens approved for this tokenIn"
        );

        if (approvedBalance < amount || amount == 0) {
            amount = approvedBalance;
        }

        if (actualBalance < amount) {
            amount = actualBalance;
        }

        tokenIn.safeTransferFrom(user, address(this), amount);
        amount = tokenIn.balanceOf(address(this));

        uint256 swapAmount = calcAndTransferFee(path[0], amount);

        tokenIn.safeApprove(address(uniswapRouter), swapAmount);

        uint256[] memory amounts = uniswapRouter.getAmountsOut(
            swapAmount,
            path
        );

        uint256 amountOutMin = amounts[amounts.length - 1].sub(
            amounts[amounts.length - 1].mul(slippage).div(SLIPPAGE_PRECISION)
        );

        return _swap(user, path, swapAmount, amountOutMin);
    }

    /**
     * @notice Internal function that swaps specified tokenIn amount to tokenOut
     * @param to: address to send the swapped tokens to
     * @param path: path for token swap
     * @param amount: Amount of tokenIn
     * @param amountOutMin: Minimum amount to recieve or revert
     */
    function _swap(
        address to,
        address[] calldata path,
        uint256 amount,
        uint256 amountOutMin
    ) internal returns (bool) {
        uint256[] memory amounts;
        {
            if (path[path.length - 1] == uniswapRouter.WETH()) {
                amounts = uniswapRouter.swapExactTokensForETH(
                    amount,
                    amountOutMin,
                    path,
                    to,
                    deadline
                );
            } else {
                amounts = uniswapRouter.swapExactTokensForTokens(
                    amount,
                    amountOutMin,
                    path,
                    to,
                    deadline
                );
            }

            emit tokenSwapped(
                to,
                path[0],
                path[path.length - 1],
                amounts[0],
                amounts[1]
            );

            return true;
        }
    }

    /**
     * @notice Updates uniswap router contract, can only be called by owner of the contract
     * @param uniswapRouterAddress: New uniswap router address
     */
    function updateRouterContract(address uniswapRouterAddress)
        external
        onlyOwner
        returns (bool)
    {
        uniswapRouter = IUniswapV2Router02(uniswapRouterAddress);
        return true;
    }

    function getAgentManager() external override view returns (address) {
        return address(agentManager);
    }
}
/**
 * @title Proxy
 * @notice This contract is responsible for enabling agents to swap tokems
 * in uniswap pool on behalf of the users
 * @dev Inrteracts with agent manager contract for agent registration & authorization
 * the modifier agentVerified deals with that
 */
function swap(
    string calldata agentId,
    address user,
    address[] calldata path,
    uint256 amount,
    uint256 slippage
) external override nonReentrant agentVerified(agentId, user) returns (bool) {
    IERC20 tokenIn = IERC20(path[0]);

    uint256 approvedBalance = tokenIn.allowance(user, address(this));
    uint256 actualBalance = tokenIn.balanceOf(user);

    require(
        actualBalance > 0 && approvedBalance > 0,
        "User has no tokens approved for this tokenIn"
    );

    if (approvedBalance < amount || amount == 0) {
        amount = approvedBalance;
    }

    if (actualBalance < amount) {
        amount = actualBalance;
    }

    tokenIn.safeTransferFrom(user, address(this), amount);
    amount = tokenIn.balanceOf(address(this));

    uint256 swapAmount = calcAndTransferFee(path[0], amount);

    tokenIn.safeApprove(address(uniswapRouter), swapAmount);

    uint256[] memory amounts = uniswapRouter.getAmountsOut(
        swapAmount,
        path
    );

    uint256 amountOutMin = amounts[amounts.length - 1].sub(
        amounts[amounts.length - 1].mul(slippage).div(SLIPPAGE_PRECISION)
    );

    return _swap(user, path, swapAmount, amountOutMin);
}

/**
 * @notice Function that swaps _tokenIn amount as much approved by the user to this contract for _tokenOut
 * If approval > _tokenIn balance, all of the _tokenIn amount/balance is swapped
 * @param agentId: unique id of the agent doing the transaction
 * @param user: user address to withdraw liquidity for
 * @param path: path for token swap
 * @param slippage: Slippage tolerance to consider
 */