contract ChainlinkAdapterOracleV2 is IBaseOracle, Governable {
  using SafeMath for uint;
  using SafeCast for int;

  event SetMaxDelayTime(address indexed token, uint maxDelayTime);
  event SetTokenRemapping(address indexed token, address indexed remappedToken);
  event SetRemappedTokenDecimal(address indexed token, uint8 decimal);

  // Chainlink denominations (source: https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.8/Denominations.sol)
  IFeedRegistry public constant registry =
    IFeedRegistry(0x47Fb2585D2C56Fe188D0E6ec628a38b74fCeeeDf);
  address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
  address public constant BTC = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;
  address public constant USD = address(840);

  mapping(address => address) public remappedTokens; // Mapping from original token to remapped token for price querying (e.g. WBTC -> BTC, renBTC -> BTC)
  mapping(address => uint8) public remappedTokenDecimals; // Mapping from remapped token to decimals
  mapping(address => uint) public maxDelayTimes; // Mapping from token address to max delay time

  constructor() public {
    __Governable__init();
    remappedTokenDecimals[ETH] = 18;
    remappedTokenDecimals[BTC] = 8;
    emit SetRemappedTokenDecimal(ETH, 18);
    emit SetRemappedTokenDecimal(BTC, 8);
  }

  /// @dev Set max delay time for each token
  /// @param _remappedTokens List of remapped tokens to set max delay
  /// @param _maxDelayTimes List of max delay times to set to
  function setMaxDelayTimes(address[] calldata _remappedTokens, uint[] calldata _maxDelayTimes)
    external
    onlyGov
  {
    require(
      _remappedTokens.length == _maxDelayTimes.length,
      '_remappedTokens & _maxDelayTimes length mismatched'
    );
    for (uint idx = 0; idx < _remappedTokens.length; idx++) {
      maxDelayTimes[_remappedTokens[idx]] = _maxDelayTimes[idx];
      emit SetMaxDelayTime(_remappedTokens[idx], _maxDelayTimes[idx]);
    }
  }

  /// @dev Set decimal for remapped tokens, e.g. ETH, BTC
  /// @param _remappedTokens List of remapped tokens to set decimals
  /// @param _decimals List of decimals to set
  function setRemappedTokenDecimals(address[] calldata _remappedTokens, uint8[] calldata _decimals)
    external
    onlyGov
  {
    require(
      _remappedTokens.length == _decimals.length,
      '_remappedTokens & _decimals length mismatched'
    );
    for (uint idx = 0; idx < _remappedTokens.length; idx++) {
      remappedTokenDecimals[_remappedTokens[idx]] = _decimals[idx];
      emit SetRemappedTokenDecimal(_remappedTokens[idx], _decimals[idx]);
    }
  }

  /// @dev Set token remapping
  /// @param _tokens List of tokens to set remapping
  /// @param _remappedTokens List of tokens to set remapping to
  /// @notice Token decimals of the original and remapped tokens should be the same
  function setTokenRemappings(address[] calldata _tokens, address[] calldata _remappedTokens)
    external
    onlyGov
  {
    require(
      _tokens.length == _remappedTokens.length,
      '_tokens & _remappedTokens length mismatched'
    );
    for (uint idx = 0; idx < _tokens.length; idx++) {
      require(
        ChainlinkDetailedERC20(_tokens[idx]).decimals() ==
          remappedTokenDecimals[_remappedTokens[idx]],
        'incorrect token decimals'
      );
      remappedTokens[_tokens[idx]] = _remappedTokens[idx];
      emit SetTokenRemapping(_tokens[idx], _remappedTokens[idx]);
    }
  }

  /// @dev Return token decimals (use remappedTokenDecimals if possible, otherwise use ERC20.decimals)
  /// @param _token Token to get decimals for
  function getRemappedTokenDecimals(address _token) public view returns (uint8) {
    uint8 decimals = remappedTokenDecimals[_token];
    if (decimals > 0) return decimals;
    return ChainlinkDetailedERC20(_token).decimals();
  }

  /// @dev Return token price in ETH, multiplied by 2**112
  /// @param _token Token address to get price of
  function getETHPx(address _token) external view override returns (uint) {
    // remap token if possible
    address token = remappedTokens[_token];
    if (token == address(0)) token = _token;

    if (token == ETH) return uint(2**112);

    uint decimals = getRemappedTokenDecimals(token);

    uint maxDelayTime = maxDelayTimes[token];
    require(maxDelayTime != 0, 'max delay time not set');

    // try to get token-ETH price
    // if feed not available, use token-USD price with ETH-USD
    try registry.latestRoundData(token, ETH) returns (
      uint80,
      int answer,
      uint,
      uint updatedAt,
      uint80
    ) {
      require(updatedAt >= block.timestamp.sub(maxDelayTime), 'delayed token-eth update time');
      return answer.toUint256().mul(2**112).div(10**decimals);
    } catch {
      (, int answer, , uint updatedAt, ) = registry.latestRoundData(token, USD);
      require(updatedAt >= block.timestamp.sub(maxDelayTime), 'delayed token-usd update time');
      (, int ethAnswer, , uint ethUpdatedAt, ) = registry.latestRoundData(ETH, USD);
      require(
        ethUpdatedAt >= block.timestamp.sub(maxDelayTimes[ETH]),
        'delayed eth-usd update time'
      );

      if (decimals > 18) {
        return answer.toUint256().mul(2**112).div(ethAnswer.toUint256()).div(10**(decimals - 18));
      } else {
        return answer.toUint256().mul(2**112).mul(10**(18 - decimals)).div(ethAnswer.toUint256());
      }
    }

    revert('no valid price reference for token');
  }
}
function setRemappedTokenDecimals(address[] calldata _remappedTokens, uint8[] calldata _decimals)
  external
  onlyGov
{
  require(
    _remappedTokens.length == _decimals.length,
    '_remappedTokens & _decimals length mismatched'
  );
  for (uint idx = 0; idx < _remappedTokens.length; idx++) {
    remappedTokenDecimals[_remappedTokens[idx]] = _decimals[idx];
    emit SetRemappedTokenDecimal(_remappedTokens[idx], _decimals[idx]);
  }
}

/// @dev Set decimal for remapped tokens, e.g. ETH, BTC
/// @param _remappedTokens List of remapped tokens to set decimals
/// @param _decimals List of decimals to set