contract UniswapMakerBroker is MakerBrokerBase {
    using UniswapFactoryHelper for IUniswapFactory;

    address public wethTokenAddress;
    address public loopringDelegate;
    IUniswapFactory public uniswapFactory;

    mapping(address => address) public tokenToExchange;
    mapping(address => bool) public tokenToIsSetup;

    constructor(address _loopringDelegate, address _uniswapFactory, address _wethTokenAddress) public {
        loopringDelegate = _loopringDelegate;
        wethTokenAddress = _wethTokenAddress;
        uniswapFactory = IUniswapFactory(_uniswapFactory);
    }

    function setupToken(address token, bool setupExchange) public {
        if (setupExchange) {
            IUniswapExchange exchange = uniswapFactory.exchangeOf(token);
            ERC20(token).approve(address(exchange), uint(-1));
            tokenToExchange[token] = address(exchange);
        }
        ERC20(token).approve(loopringDelegate, uint(-1));
        tokenToIsSetup[token] = true;
    }

    function setupTokens(address[] calldata tokens, bool[] calldata setupExchanges) external {
        require(tokens.length == setupExchanges.length, "lengths do not match");

        for (uint i = 0; i < tokens.length; i++) {
            setupToken(tokens[i], setupExchanges[i]);
        }
    }

    function() external payable {
        // No op, but accepts ETH being sent to this contract.
    }

    // --------------------------------
    // Loopring Broker Delegate

    function brokerRequestAllowance(BrokerData.BrokerApprovalRequest memory request) public returns (bool) {
        require(msg.sender == loopringDelegate, "Uniswap MakerBroker: Unauthorized caller");
        require(tokenToIsSetup[request.tokenS], "Uniswap MakerBroker: tokenS is not setup yet");

        for (uint i = 0; i < request.orders.length; i++) {
            require(request.orders[i].tokenRecipient == address(this), "Uniswap MakerBroker: Order tokenRecipient must be this broker");
            require(request.orders[i].owner == owner, "Uniswap MakerBroker: Order owner must be the owner of this contract");
        }

        if (request.tokenB == wethTokenAddress) {
            // We need to convert WETH to ETH to 1) avoid double fee payment on Uniswap and 2) get the best price on Uniswap
            IWETH(wethTokenAddress).withdraw(request.totalFillAmountB);
        }

        // perform exchange
        bool isTokenBExchangeWrapper = abi.decode(request.orders[0].extraData, (bool));
        IUniswapExchange exchange;
        if (isTokenBExchangeWrapper) {
            exchange = IUniswapExchange(address(uint160(tokenToExchange[request.tokenB])));
        } else {
            exchange = IUniswapExchange(address(uint160(tokenToExchange[request.tokenS])));
        }

        (bool success, bytes memory result) = address(this).call(
            abi.encodePacked(
//                keccak256("performSwap(address,((address,bytes32,uint,uint,uint,address,bytes)[],address,address,address,uint,uint,uint),bool"),
                this.performSwap.selector,
                abi.encode(exchange, request, isTokenBExchangeWrapper)
            )
        );
        require(success, "Uniswap MakerBroker: Uniswap matching failed");
        uint exchangeAmount = abi.decode(result, (uint));

        if (request.tokenS == wethTokenAddress) {
            // Orders on Dolomite require we use WETH instead of ETH, so we must convert it back
            IWETH(wethTokenAddress).deposit.value(exchangeAmount)();
        }

        return false;
    }

    function performSwap(
        IUniswapExchange exchange,
        BrokerData.BrokerApprovalRequest memory request,
        bool isTokenBExchangeWrapper
    ) public returns (uint exchangeAmount) {
        // This fn is public so we can get access to the #selector with type-safety.
        require(msg.sender == address(this) || msg.sender == loopringDelegate, "INVALID_SENDER");

        uint deadline = block.timestamp + 1;
        if (request.tokenS == wethTokenAddress) {
            exchangeAmount = exchange.tokenToEthSwapInput(
                request.totalFillAmountB,
                request.totalRequestedAmountS,
                deadline
            );
        } else if (request.tokenB == wethTokenAddress) {
            exchangeAmount = exchange.ethToTokenSwapInput.value(request.totalFillAmountB)(
                request.totalRequestedAmountS,
                deadline
            );
        } else {
            // Pure token to token
            address tokenToBuy;
            if (isTokenBExchangeWrapper) {
                request.tokenS;
            } else {
                request.tokenB;
            }
            exchangeAmount = exchange.tokenToTokenSwapInput(
                request.totalFillAmountB,
                request.totalRequestedAmountS,
            /* minEthBoughtInWei */ 1, // We don't care about the token <-> ETH exchange rate as long as we get enough tokenB
                deadline,
                tokenToBuy
            );
        }

        return exchangeAmount;
    }

    function onOrderFillReport(BrokerData.BrokerInterceptorReport memory fillReport) public {
        // No op
    }

    /*
     * Balance is unknown as it is entirely dependent on the offer in Uniswap being filled.
     * Thus we send down a very large number to ensure the relay/matching engine believes
     * orders from this broker have enough of a balance for trades to be filled.
     */
    function brokerBalanceOf(address owner, address tokenAddress) public view returns (uint) {
        return uint(-1);
    }

}
/*
 * Inherits Loopring's IBrokerDelegate and sources liquidity from Uniswap
 * when the Loopring protocol requests a token approval. Because the Loopring
 * protocol expects the taker order to precede maker orders, and non-brokered
 * transfers occur before before brokered transfers, it is guaranteed that this
 * broker contract will receive the necessary tokens to trade, right before it
 * sets the approval and the Loopring protocol transfers the tokens out. Thus,
 * liquidity can be sourced on-chain with no money down!
 */
function brokerBalanceOf(address owner, address tokenAddress) public view returns (uint) {
    return uint(-1);
}

/*
 * Balance is unknown as it is entirely dependent on the offer in Uniswap being filled.
 * Thus we send down a very large number to ensure the relay/matching engine believes
 * orders from this broker have enough of a balance for trades to be filled.
 */