contract KyberCustomTokenRates is HasAdmin, KyberAdapter {
  struct Rate {
    address quote;
    uint256 value;
  }

  event CustomTokenRateUpdated(
    address indexed _tokenAddress,
    address indexed _quoteTokenAddress,
    uint256 _rate
  );

  mapping (address => Rate) public customTokenRate;

  function _hasCustomTokenRate(address _tokenAddress) internal view returns (bool _correct) {
    return customTokenRate[_tokenAddress].value > 0;
  }

  function _setCustomTokenRate(address _tokenAddress, address _quoteTokenAddress, uint256 _rate) internal {
    require(_rate > 0);
    customTokenRate[_tokenAddress] = Rate({ quote: _quoteTokenAddress, value: _rate });
    emit CustomTokenRateUpdated(_tokenAddress, _quoteTokenAddress, _rate);
  }

  // solium-disable-next-line security/no-assign-params
  function _getConversionRate(
    address _src,
    uint256 _srcAmount,
    address _dest
  )
    internal
    view
    returns (
      uint256 _expectedRate,
      uint256 _slippageRate
    )
  {
    uint256 _numerator = 1;
    uint256 _denominator = 1;

    if (_hasCustomTokenRate(_src)) {
      Rate storage _rate = customTokenRate[_src];

      _src = _rate.quote;
      _srcAmount = _srcAmount.mul(_rate.value).div(10**18);

      _numerator = _rate.value;
      _denominator = 10**18;
    }

    if (_hasCustomTokenRate(_dest)) {
      Rate storage _rate = customTokenRate[_dest];

      _dest = _rate.quote;

      // solium-disable-next-line whitespace
      if (_numerator == 1) { _numerator = 10**18; }
      _denominator = _rate.value;
    }

    if (_src != _dest) {
      (_expectedRate, _slippageRate) = super._getConversionRate(_src, _srcAmount, _dest);
    } else {
      _expectedRate = _slippageRate = 10**18;
    }

    return (
      _expectedRate.mul(_numerator).div(_denominator),
      _slippageRate.mul(_numerator).div(_denominator)
    );
  }

  function _swapToken(
    address _src,
    uint256 _maxSrcAmount,
    address _dest,
    uint256 _maxDestAmount,
    uint256 _minConversionRate,
    address payable _initiator,
    address payable _receiver
  )
    internal
    returns (
      uint256 _srcAmount,
      uint256 _destAmount
    )
  {
    if (_hasCustomTokenRate(_src) || _hasCustomTokenRate(_dest)) {
      require(_src == ethAddress ? msg.value >= _maxSrcAmount : msg.value == 0);
      require(_receiver == address(this));

      (uint256 _expectedRate, ) = _getConversionRate(_src, _srcAmount, _dest);
      require(_expectedRate >= _minConversionRate);

      _srcAmount = _maxSrcAmount;
      _destAmount = _fixTokenDecimals(_src, _dest, _srcAmount.mul(_expectedRate), false);

      if (_destAmount > _maxDestAmount) {
        _destAmount = _maxDestAmount;
        _srcAmount = _fixTokenDecimals(_dest, _src, _destAmount.mul(10**36).ceilingDiv(_expectedRate), true);

        // To avoid rounding error.
        if (_srcAmount > _maxSrcAmount) {
          _srcAmount = _maxSrcAmount;
        }
      }

      if (_src != ethAddress) {
        require(IERC20(_src).transferFrom(_initiator, address(this), _srcAmount));
      } else if (msg.value > _srcAmount) {
        _initiator.transfer(msg.value - _srcAmount);
      }

      return (_srcAmount, _destAmount);
    }

    return super._swapToken(
      _src,
      _maxSrcAmount,
      _dest,
      _maxDestAmount,
      _minConversionRate,
      _initiator,
      _receiver
    );
  }
}
function _getConversionRate(
  address _src,
  uint256 _srcAmount,
  address _dest
)
  internal
  view
  returns (
    uint256 _expectedRate,
    uint256 _slippageRate
  )
{
  uint256 _numerator = 1;
  uint256 _denominator = 1;

  if (_hasCustomTokenRate(_src)) {
    Rate storage _rate = customTokenRate[_src];

    _src = _rate.quote;
    _srcAmount = _srcAmount.mul(_rate.value).div(10**18);

    _numerator = _rate.value;
    _denominator = 10**18;
  }

  if (_hasCustomTokenRate(_dest)) {
    Rate storage _rate = customTokenRate[_dest];

    _dest = _rate.quote;

    // solium-disable-next-line whitespace
    if (_numerator == 1) { _numerator = 10**18; }
    _denominator = _rate.value;
  }

  if (_src != _dest) {
    (_expectedRate, _slippageRate) = super._getConversionRate(_src, _srcAmount, _dest);
  } else {
    _expectedRate = _slippageRate = 10**18;
  }

  return (
    _expectedRate.mul(_numerator).div(_denominator),
    _slippageRate.mul(_numerator).div(_denominator)
  );
}

// solium-disable-next-line security/no-assign-params