contract RateHelper is Withdrawable2 {

    IERC20 internal constant ETH_TOKEN_ADDRESS = IERC20(
        0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
    );

    IKyberReserve public uniswapReserve;
    IKyberReserve public oasisReserve;
    // support token -> ETH trade
    mapping(address => IKyberReserve[]) public srcTokenReserves;
    // support ETH -> token trade
    mapping(address => IKyberReserve[]) public destTokenReserves;

    constructor(address uniswap, address oasis) public Withdrawable2(msg.sender) {
        addOperator(msg.sender);
        uniswapReserve = IKyberReserve(uniswap);
        oasisReserve = IKyberReserve(oasis);
    }

    function setSrcTokenReserves(address token, IKyberReserve[] memory reserves) public onlyOperator {
        srcTokenReserves[token] = reserves;
    }

    function setDestTokenReserves(address token, IKyberReserve[] memory reserves) public onlyOperator {
        destTokenReserves[token] = reserves;
    }

    function setBridgeReserves(address _uniswap, address _oasis) public onlyOperator {
        uniswapReserve = IKyberReserve(_uniswap);
        oasisReserve = IKyberReserve(_oasis);
    }

    /// @dev return expected rates (ETH -> token) given list of amounts
    ///      exclude uniswap or oasis reserves if needed
    ///      if isBuy, amount is in ETH, otherwise amount is in token
    function getExpectedRates(IERC20 token, uint[] memory amounts, bool isExcludeUni, bool isExcludeOasis, uint blockNumber, bool isBuy)
        public view returns(uint[] memory rates)
    {
        rates = new uint[](amounts.length);
        IERC20 src = isBuy ? ETH_TOKEN_ADDRESS : token;
        IERC20 dest = isBuy ? token : ETH_TOKEN_ADDRESS;
        IKyberReserve[] memory reserves = isBuy ? destTokenReserves[address(token)] : srcTokenReserves[address(token)];
        for(uint i = 0; i < amounts.length; i++) {
            rates[i] = 0;
            for(uint j = 0; j < reserves.length; j++) {
                if (isExcludeUni && reserves[j] == uniswapReserve) { continue; }
                if (isExcludeOasis && reserves[j] == oasisReserve) { continue; }
                uint expectedRate = reserves[j].getConversionRate(src, dest, amounts[i], blockNumber);
                // get max rate
                if (rates[i] < expectedRate) { rates[i] = expectedRate; }
            }
        }
    }

    /// @dev isUniswap = true: get rate from uniswap bridge, otherwise get rate from oasis reserve
    function getBridgeReserveRates(IERC20 token, uint[] memory amounts, uint blockNumber, bool isBuy, bool isUniswap)
        public view returns(uint[] memory rates)
    {
        rates = new uint[](amounts.length);
        IERC20 src = isBuy ? ETH_TOKEN_ADDRESS : token;
        IERC20 dest = isBuy ? token : ETH_TOKEN_ADDRESS;
        IKyberReserve reserve = isUniswap ? uniswapReserve : oasisReserve;
        for(uint i = 0; i < rates.length; i++) {
            rates[i] = reserve.getConversionRate(src, dest, amounts[i], blockNumber);
        }
    }

    // return Kyber's rate excludes uniswap + oasis
    // return uniswap rates
    // return oasis rates
    function getCombinedRates(IERC20 token, uint[] memory amounts, uint blockNumber, bool isBuy)
        public view returns(uint[] memory rates, uint[] memory uniswapRates, uint[] memory oasisRates)
    {
        rates = new uint[](amounts.length);
        uniswapRates = new uint[](amounts.length);
        oasisRates = new uint[](amounts.length);
        IERC20 src = isBuy ? ETH_TOKEN_ADDRESS : token;
        IERC20 dest = isBuy ? token : ETH_TOKEN_ADDRESS;
        IKyberReserve[] memory reserves = isBuy ? destTokenReserves[address(token)] : srcTokenReserves[address(token)];
        for(uint i = 0; i < amounts.length; i++) {
            rates[i] = 0;
            uniswapRates[i] = 0;
            oasisRates[i] = 0;
            for(uint j = 0; j < reserves.length; j++) {
                uint expectedRate = reserves[j].getConversionRate(src, dest, amounts[i], blockNumber);
                if (reserves[j] == uniswapReserve) {
                    uniswapRates[i] = expectedRate;
                } else if (reserves[j] == oasisReserve) {
                    oasisRates[i] = expectedRate;
                } else {
                    if (rates[i] < expectedRate) { rates[i] = expectedRate; }
                }
            }
        }
    }
}
function getExpectedRates(IERC20 token, uint[] memory amounts, bool isExcludeUni, bool isExcludeOasis, uint blockNumber, bool isBuy)
    public view returns(uint[] memory rates)
{
    rates = new uint[](amounts.length);
    IERC20 src = isBuy ? ETH_TOKEN_ADDRESS : token;
    IERC20 dest = isBuy ? token : ETH_TOKEN_ADDRESS;
    IKyberReserve[] memory reserves = isBuy ? destTokenReserves[address(token)] : srcTokenReserves[address(token)];
    for(uint i = 0; i < amounts.length; i++) {
        rates[i] = 0;
        for(uint j = 0; j < reserves.length; j++) {
            if (isExcludeUni && reserves[j] == uniswapReserve) { continue; }
            if (isExcludeOasis && reserves[j] == oasisReserve) { continue; }
            uint expectedRate = reserves[j].getConversionRate(src, dest, amounts[i], blockNumber);
            // get max rate
            if (rates[i] < expectedRate) { rates[i] = expectedRate; }
        }
    }
}

/// @dev return expected rates (ETH -> token) given list of amounts
///      exclude uniswap or oasis reserves if needed
///      if isBuy, amount is in ETH, otherwise amount is in token